From 8542b2f6a27c00faa18c5df0ac78d31ec7738426 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 11 Dec 2025 09:26:35 +0300 Subject: [PATCH 001/297] Live: Move dashboard events from the raw http server to the apiserver (#115066) --- pkg/api/dashboard.go | 32 ------- .../apis/dashboard/dashboard_storage.go | 40 +++++++-- pkg/registry/apis/dashboard/register.go | 9 +- pkg/server/wire.go | 1 + pkg/server/wire_gen.go | 8 +- pkg/services/live/features/dashboard.go | 87 +++---------------- pkg/services/live/live.go | 23 ++--- public/app/features/live/dashboard/types.ts | 1 - 8 files changed, 66 insertions(+), 135 deletions(-) diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 5ebbca411eb..3b764568e75 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -380,13 +380,6 @@ func (hs *HTTPServer) deleteDashboard(c *contextmodel.ReqContext) response.Respo return dashboardErrResponse(err, "Failed to delete dashboard") } - if hs.Live != nil { - err := hs.Live.GrafanaScope.Dashboards.DashboardDeleted(c.GetOrgID(), c.SignedInUser, dash.UID) - if err != nil { - hs.log.Error("Failed to broadcast delete info", "dashboard", dash.UID, "error", err) - } - } - return response.JSON(http.StatusOK, util.DynMap{ "title": dash.Title, "message": fmt.Sprintf("Dashboard %s deleted", dash.Title), @@ -482,31 +475,6 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S } dashboard, saveErr := hs.DashboardService.SaveDashboard(ctx, dashItem, allowUiUpdate) - - if hs.Live != nil { - // Tell everyone listening that the dashboard changed - if dashboard == nil { - dashboard = dash // the original request - } - - // This will broadcast all save requests only if a `gitops` observer exists. - // gitops is useful when trying to save dashboards in an environment where the user can not save - channel := hs.Live.GrafanaScope.Dashboards - liveerr := channel.DashboardSaved(c.GetOrgID(), c.SignedInUser, cmd.Message, dashboard, saveErr) - - // When an error exists, but the value broadcast to a gitops listener return 202 - if liveerr == nil && saveErr != nil && channel.HasGitOpsObserver(c.GetOrgID()) { - return response.JSON(http.StatusAccepted, util.DynMap{ - "status": "pending", - "message": "changes were broadcast to the gitops listener", - }) - } - - if liveerr != nil { - hs.log.Warn("Unable to broadcast save event", "uid", dashboard.UID, "error", liveerr) - } - } - if saveErr != nil { return apierrors.ToDashboardErrorResponse(ctx, hs.pluginStore, saveErr) } diff --git a/pkg/registry/apis/dashboard/dashboard_storage.go b/pkg/registry/apis/dashboard/dashboard_storage.go index d2d54c8d815..7ab91f8ec66 100644 --- a/pkg/registry/apis/dashboard/dashboard_storage.go +++ b/pkg/registry/apis/dashboard/dashboard_storage.go @@ -7,20 +7,41 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" + "github.com/grafana/grafana-app-sdk/logging" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/grafana/pkg/services/live" ) -// dashboardStoragePermissionWrapper is a wrapper around the grafanarest.Storage that adds dashboard permissions handling -// when dual writing is enabled. -type dashboardStoragePermissionWrapper struct { - dashboardPermissionsSvc accesscontrol.DashboardPermissionsService +// dashboardStorageWrapper is a wrapper around the grafanarest.Storage so it will: +// 1. support adds dashboard permissions handling +// 2. broadcast changes to grafana live +// when running in single tenant mode +type dashboardStorageWrapper struct { grafanarest.Storage + + dashboardPermissionsSvc accesscontrol.DashboardPermissionsService + live live.DashboardActivityChannel } -func (d dashboardStoragePermissionWrapper) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - info, err := request.NamespaceInfoFrom(ctx, true) +func (d dashboardStorageWrapper) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + ns, err := request.NamespaceInfoFrom(ctx, true) + if err != nil { + return nil, false, err + } + + obj, created, err := d.Storage.Update(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options) + if err == nil && ns.OrgID > 0 && d.live != nil { + if err := d.live.DashboardSaved(ns.OrgID, name); err != nil { + logging.FromContext(ctx).Info("live dashboard update failed", "err", err) + } + } + return obj, created, err +} + +func (d dashboardStorageWrapper) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, false, err } @@ -28,7 +49,12 @@ func (d dashboardStoragePermissionWrapper) Delete(ctx context.Context, name stri if err != nil { return obj, async, err } - if accessErr := d.dashboardPermissionsSvc.DeleteResourcePermissions(ctx, info.OrgID, name); accessErr != nil { + if ns.OrgID > 0 && d.live != nil { + if err := d.live.DashboardDeleted(ns.OrgID, name); err != nil { + logging.FromContext(ctx).Info("live dashboard update failed", "err", err) + } + } + if accessErr := d.dashboardPermissionsSvc.DeleteResourcePermissions(ctx, ns.OrgID, name); accessErr != nil { return obj, async, accessErr } return obj, async, nil diff --git a/pkg/registry/apis/dashboard/register.go b/pkg/registry/apis/dashboard/register.go index c0cd241c1c4..bd732a70b08 100644 --- a/pkg/registry/apis/dashboard/register.go +++ b/pkg/registry/apis/dashboard/register.go @@ -56,6 +56,7 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/libraryelements" "github.com/grafana/grafana/pkg/services/librarypanels" + "github.com/grafana/grafana/pkg/services/live" "github.com/grafana/grafana/pkg/services/provisioning" "github.com/grafana/grafana/pkg/services/publicdashboards" "github.com/grafana/grafana/pkg/services/quota" @@ -122,6 +123,7 @@ type DashboardsAPIBuilder struct { snapshotService dashboardsnapshots.Service snapshotOptions dashv0.SnapshotSharingOptions namespacer request.NamespaceMapper + dashboardActivityChannel live.DashboardActivityChannel isStandalone bool // skips any handling including anything to do with legacy storage } @@ -151,6 +153,7 @@ func RegisterAPIService( libraryPanels libraryelements.Service, publicDashboardService publicdashboards.Service, snapshotService dashboardsnapshots.Service, + dashboardActivityChannel live.DashboardActivityChannel, ) *DashboardsAPIBuilder { dbp := legacysql.NewDatabaseProvider(sql) namespacer := request.GetNamespaceMapper(cfg) @@ -185,6 +188,7 @@ func RegisterAPIService( snapshotService: snapshotService, snapshotOptions: snapshotOptions, namespacer: namespacer, + dashboardActivityChannel: dashboardActivityChannel, legacy: &DashboardStorage{ Access: legacy.NewDashboardSQLAccess(dbp, namespacer, dashStore, provisioning, libraryPanelSvc, sorter, dashboardPermissionsSvc, accessControl, features), DashboardService: dashboardService, @@ -696,9 +700,10 @@ func (b *DashboardsAPIBuilder) storageForVersion( if err != nil { return err } - storage[dashboards.StoragePath()] = dashboardStoragePermissionWrapper{ - dashboardPermissionsSvc: b.dashboardPermissionsSvc, + storage[dashboards.StoragePath()] = dashboardStorageWrapper{ Storage: dw, + dashboardPermissionsSvc: b.dashboardPermissionsSvc, + live: b.dashboardActivityChannel, } // Register the DTO endpoint that will consolidate all dashboard bits diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 0d4bb10c0b5..970d1c003c9 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -280,6 +280,7 @@ var wireBasicSet = wire.NewSet( store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, + live.ProvideDashboardActivityChannel, pushhttp.ProvideService, contexthandler.ProvideService, ldapservice.ProvideService, diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 8500130aeea..1406e6fdbf8 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -870,7 +870,8 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api identitySynchronizer := authnimpl.ProvideIdentitySynchronizer(authnimplService) ldapImpl := service12.ProvideService(cfg, featureToggles, ssosettingsimplService) apiService := api4.ProvideService(cfg, routeRegisterImpl, accessControl, userService, authinfoimplService, ossGroups, identitySynchronizer, orgService, ldapImpl, userAuthTokenService, bundleregistryService) - dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service15, dashboardServiceImpl, dashboardPermissionsService, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, libraryPanelService, eventualRestConfigProvider, userService, libraryElementService, publicDashboardServiceImpl, serviceImpl) + dashboardActivityChannel := live.ProvideDashboardActivityChannel(grafanaLive) + dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service15, dashboardServiceImpl, dashboardPermissionsService, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, libraryPanelService, eventualRestConfigProvider, userService, libraryElementService, publicDashboardServiceImpl, serviceImpl, dashboardActivityChannel) dataSourceAPIBuilder, err := datasource.RegisterAPIService(featureToggles, apiserverService, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, accessControl, registerer, sourcesService) if err != nil { return nil, err @@ -1527,7 +1528,8 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac identitySynchronizer := authnimpl.ProvideIdentitySynchronizer(authnimplService) ldapImpl := service12.ProvideService(cfg, featureToggles, ssosettingsimplService) apiService := api4.ProvideService(cfg, routeRegisterImpl, accessControl, userService, authinfoimplService, ossGroups, identitySynchronizer, orgService, ldapImpl, userAuthTokenService, bundleregistryService) - dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service15, dashboardServiceImpl, dashboardPermissionsService, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, libraryPanelService, eventualRestConfigProvider, userService, libraryElementService, publicDashboardServiceImpl, serviceImpl) + dashboardActivityChannel := live.ProvideDashboardActivityChannel(grafanaLive) + dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service15, dashboardServiceImpl, dashboardPermissionsService, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, libraryPanelService, eventualRestConfigProvider, userService, libraryElementService, publicDashboardServiceImpl, serviceImpl, dashboardActivityChannel) dataSourceAPIBuilder, err := datasource.RegisterAPIService(featureToggles, apiserverService, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, accessControl, registerer, sourcesService) if err != nil { return nil, err @@ -1791,7 +1793,7 @@ var withOTelSet = wire.NewSet( otelTracer, grpcserver.ProvideService, interceptors.ProvideAuthenticator, ) -var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator3.ProvideService, provisioning.ProvideStubProvisioningService, legacy.ProvideMigratorDashboardAccessor, migrations2.ProvideUnifiedMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, wire.Bind(new(installsync.ServerLock), new(*serverlock.ServerLockService)), annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service12.ProvideService, wire.Bind(new(service12.LDAP), new(*service12.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service9.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service9.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets.Store), new(*database.SecretsStoreImpl)), garbagecollectionworker.ProvideWorker, grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database5.DashboardSnapshotStore)), database5.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service10.ServiceImpl)), service10.ProvideService, service9.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service9.Service)), service9.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager3.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), dsquerierclient.NewNullQSDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service7.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service7.DashboardServiceImpl)), service7.ProvideDashboardService, service7.ProvideDashboardProvisioningService, service7.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), wire.Bind(new(folder.LegacyService), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), service11.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service11.ImportDashboardService)), service8.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service8.Service)), service8.ProvideDashboardUpdater, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations3.ProvideDataSourceMigrationService, migrations3.ProvideSecretMigrationProvider, wire.Bind(new(migrations3.SecretMigrationProvider), new(*migrations3.SecretMigrationProviderImpl)), promtypemigration.ProvideAzurePromMigrationService, promtypemigration.ProvideAmazonPromMigrationService, promtypemigration.ProvidePromTypeMigrationProvider, wire.Bind(new(promtypemigration.PromTypeMigrationProvider), new(*promtypemigration.PromTypeMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, accesscontrol.ProvideFixedRolesLoader, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, caching.ProvideCachingServiceClient, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, decrypt.ProvideDecryptAuthorizer, wire.Value([]decrypt.ExtraOwnerDecrypter(nil)), decrypt.ProvideDecryptService, inline.ProvideInlineSecureValueService, encryption.ProvideDataKeyStorage, encryption.ProvideGlobalDataKeyStorage, encryption.ProvideEncryptedValueStorage, encryption.ProvideGlobalEncryptedValueStorage, encryption.ProvideEncryptedValueMigrationExecutor, service5.ProvideSecureValueService, validator.ProvideKeeperValidator, validator.ProvideSecureValueValidator, mutator.ProvideKeeperMutator, mutator.ProvideSecureValueMutator, migrator.NewWithEngine, database4.ProvideDatabase, clock.ProvideClock, wire.Bind(new(contracts.Database), new(*database4.Database)), wire.Bind(new(contracts.Clock), new(*clock.Clock)), manager2.ProvideEncryptionManager, service4.ProvideAESGCMCipherService, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, migrations2.ProvideUnifiedStorageMigrationService, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet, client.ProvideK8sClientWithFallback) +var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator3.ProvideService, provisioning.ProvideStubProvisioningService, legacy.ProvideMigratorDashboardAccessor, migrations2.ProvideUnifiedMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, wire.Bind(new(installsync.ServerLock), new(*serverlock.ServerLockService)), annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, live.ProvideDashboardActivityChannel, pushhttp.ProvideService, contexthandler.ProvideService, service12.ProvideService, wire.Bind(new(service12.LDAP), new(*service12.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service9.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service9.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets.Store), new(*database.SecretsStoreImpl)), garbagecollectionworker.ProvideWorker, grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database5.DashboardSnapshotStore)), database5.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service10.ServiceImpl)), service10.ProvideService, service9.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service9.Service)), service9.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager3.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), dsquerierclient.NewNullQSDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service7.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service7.DashboardServiceImpl)), service7.ProvideDashboardService, service7.ProvideDashboardProvisioningService, service7.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), wire.Bind(new(folder.LegacyService), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), service11.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service11.ImportDashboardService)), service8.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service8.Service)), service8.ProvideDashboardUpdater, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations3.ProvideDataSourceMigrationService, migrations3.ProvideSecretMigrationProvider, wire.Bind(new(migrations3.SecretMigrationProvider), new(*migrations3.SecretMigrationProviderImpl)), promtypemigration.ProvideAzurePromMigrationService, promtypemigration.ProvideAmazonPromMigrationService, promtypemigration.ProvidePromTypeMigrationProvider, wire.Bind(new(promtypemigration.PromTypeMigrationProvider), new(*promtypemigration.PromTypeMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, accesscontrol.ProvideFixedRolesLoader, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, caching.ProvideCachingServiceClient, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, decrypt.ProvideDecryptAuthorizer, wire.Value([]decrypt.ExtraOwnerDecrypter(nil)), decrypt.ProvideDecryptService, inline.ProvideInlineSecureValueService, encryption.ProvideDataKeyStorage, encryption.ProvideGlobalDataKeyStorage, encryption.ProvideEncryptedValueStorage, encryption.ProvideGlobalEncryptedValueStorage, encryption.ProvideEncryptedValueMigrationExecutor, service5.ProvideSecureValueService, validator.ProvideKeeperValidator, validator.ProvideSecureValueValidator, mutator.ProvideKeeperMutator, mutator.ProvideSecureValueMutator, migrator.NewWithEngine, database4.ProvideDatabase, clock.ProvideClock, wire.Bind(new(contracts.Database), new(*database4.Database)), wire.Bind(new(contracts.Clock), new(*clock.Clock)), manager2.ProvideEncryptionManager, service4.ProvideAESGCMCipherService, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, migrations2.ProvideUnifiedStorageMigrationService, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet, client.ProvideK8sClientWithFallback) var wireSet = wire.NewSet( wireBasicSet, metrics.WireSet, sqlstore.ProvideService, metrics2.ProvideService, wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), prefimpl.ProvideService, oauthtoken.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)), wire.Bind(new(cleanup.AlertRuleService), new(*store2.DBstore)), diff --git a/pkg/services/live/features/dashboard.go b/pkg/services/live/features/dashboard.go index 286dad7c49c..cda4893c258 100644 --- a/pkg/services/live/features/dashboard.go +++ b/pkg/services/live/features/dashboard.go @@ -22,39 +22,13 @@ const ( ActionDeleted actionType = "deleted" EditingStarted actionType = "editing-started" //EditingFinished actionType = "editing-finished" - - GitopsChannel = "grafana/dashboard/gitops" ) -type userDisplayDTO struct { - ID int64 `json:"id,omitempty"` - UID string `json:"uid,omitempty"` - Name string `json:"name,omitempty"` - Login string `json:"login,omitempty"` - AvatarURL string `json:"avatarUrl"` -} - -// Static function to parse a requester into a userDisplayDTO -func newUserDisplayDTOFromRequester(requester identity.Requester) *userDisplayDTO { - // nolint:staticcheck - userID, _ := requester.GetInternalID() - return &userDisplayDTO{ - ID: userID, - UID: requester.GetRawIdentifier(), - Login: requester.GetLogin(), - Name: requester.GetName(), - } -} - // DashboardEvent events related to dashboards type dashboardEvent struct { - UID string `json:"uid"` - Action actionType `json:"action"` // saved, editing, deleted - User *userDisplayDTO `json:"user,omitempty"` - SessionID string `json:"sessionId,omitempty"` - Message string `json:"message,omitempty"` - Dashboard *dashboards.Dashboard `json:"dashboard,omitempty"` - Error string `json:"error,omitempty"` + UID string `json:"uid"` + Action actionType `json:"action"` // saved, editing, deleted + SessionID string `json:"sessionId,omitempty"` } // DashboardHandler manages all the `grafana/dashboard/*` channels @@ -134,9 +108,6 @@ func (h *DashboardHandler) OnPublish(ctx context.Context, requester identity.Req return model.PublishReply{}, backend.PublishStreamStatusNotFound, nil // NOOP } - // Tell everyone who is editing - event.User = newUserDisplayDTOFromRequester(requester) - msg, err := json.Marshal(event) if err != nil { return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("internal error") @@ -153,55 +124,21 @@ func (h *DashboardHandler) publish(orgID int64, event dashboardEvent) error { if err != nil { return err } - - // Only broadcast non-error events - if event.Error == "" { - err = h.Publisher(orgID, "grafana/dashboard/uid/"+event.UID, msg) - if err != nil { - return err - } - } - - // Send everything to the gitops channel - return h.Publisher(orgID, GitopsChannel, msg) + return h.Publisher(orgID, "grafana/dashboard/uid/"+event.UID, msg) } // DashboardSaved will broadcast to all connected dashboards -func (h *DashboardHandler) DashboardSaved(orgID int64, requester identity.Requester, message string, dashboard *dashboards.Dashboard, err error) error { - if err != nil && !h.HasGitOpsObserver(orgID) { - return nil // only broadcast if it was OK - } - - msg := dashboardEvent{ - UID: dashboard.UID, - Action: ActionSaved, - User: newUserDisplayDTOFromRequester(requester), - Message: message, - Dashboard: dashboard, - } - - if err != nil { - msg.Error = err.Error() - } - - return h.publish(orgID, msg) -} - -// DashboardDeleted will broadcast to all connected dashboards -func (h *DashboardHandler) DashboardDeleted(orgID int64, requester identity.Requester, uid string) error { +func (h *DashboardHandler) DashboardSaved(orgID int64, uid string) error { return h.publish(orgID, dashboardEvent{ UID: uid, - Action: ActionDeleted, - User: newUserDisplayDTOFromRequester(requester), + Action: ActionSaved, }) } -// HasGitOpsObserver will return true if anyone is listening to the `gitops` channel -func (h *DashboardHandler) HasGitOpsObserver(orgID int64) bool { - count, err := h.ClientCount(orgID, GitopsChannel) - if err != nil { - logger.Error("Error getting client count", "error", err) - return false - } - return count > 0 +// DashboardDeleted will broadcast to all connected dashboards +func (h *DashboardHandler) DashboardDeleted(orgID int64, uid string) error { + return h.publish(orgID, dashboardEvent{ + UID: uid, + Action: ActionDeleted, + }) } diff --git a/pkg/services/live/live.go b/pkg/services/live/live.go index 4d91eae3e5e..cffdaaf0ec6 100644 --- a/pkg/services/live/live.go +++ b/pkg/services/live/live.go @@ -513,18 +513,17 @@ type GrafanaLive struct { // DashboardActivityChannel is a service to advertise dashboard activity type DashboardActivityChannel interface { - // Called when a dashboard is saved -- this includes the error so we can support a - // gitops workflow that knows if the value was saved to the local database or not - // in many cases all direct save requests will fail, but the request should be forwarded - // to any gitops observers - DashboardSaved(orgID int64, requester identity.Requester, message string, dashboard *dashboards.Dashboard, err error) error + // Called when a dashboard is saved + DashboardSaved(orgID int64, uid string) error // Called when a dashboard is deleted - DashboardDeleted(orgID int64, requester identity.Requester, uid string) error + DashboardDeleted(orgID int64, uid string) error +} - // Experimental! Indicate is GitOps is active. This really means - // someone is subscribed to the `grafana/dashboards/gitops` channel - HasGitOpsObserver(orgID int64) bool +// ProvideDashboardActivityChannel extracts the DashboardActivityChannel from GrafanaLive. +// This is used by wire to inject the channel into the dashboard API service. +func ProvideDashboardActivityChannel(live *GrafanaLive) DashboardActivityChannel { + return live.GrafanaScope.Dashboards } func (g *GrafanaLive) getStreamPlugin(ctx context.Context, pluginID string) (backend.StreamHandler, error) { @@ -1178,12 +1177,6 @@ func (g *GrafanaLive) HandleListHTTP(c *contextmodel.ReqContext) response.Respon // HandleInfoHTTP special http response for func (g *GrafanaLive) HandleInfoHTTP(ctx *contextmodel.ReqContext) response.Response { - path := web.Params(ctx.Req)["*"] - if path == "grafana/dashboards/gitops" { - return response.JSON(http.StatusOK, util.DynMap{ - "active": g.GrafanaScope.Dashboards.HasGitOpsObserver(ctx.GetOrgID()), - }) - } return response.JSONStreaming(http.StatusNotFound, util.DynMap{ "message": "Info is not supported for this channel", }) diff --git a/public/app/features/live/dashboard/types.ts b/public/app/features/live/dashboard/types.ts index 447fc2c9101..cffe686fc27 100644 --- a/public/app/features/live/dashboard/types.ts +++ b/public/app/features/live/dashboard/types.ts @@ -8,7 +8,6 @@ export enum DashboardEventAction { export interface DashboardEvent { uid: string; action: DashboardEventAction; - userId?: number; message?: string; sessionId?: string; timestamp?: number; From 2fc1210b38e94574790fae1a9ac94abd186a710a Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 11 Dec 2025 09:36:09 +0300 Subject: [PATCH 002/297] Stars: Enable the collections apiserver (#115076) --- .../pkg/apis/collections/v1alpha1/stars.go | 16 +++++++++++----- .../pkg/apis/collections/v1alpha1/stars_test.go | 2 +- pkg/registry/apis/collections/legacy/stars.go | 5 ++++- pkg/registry/apis/collections/register.go | 6 ------ pkg/tests/apis/collections/stars_test.go | 9 +++++++-- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/apps/collections/pkg/apis/collections/v1alpha1/stars.go b/apps/collections/pkg/apis/collections/v1alpha1/stars.go index e203e6b560e..79bff8a6513 100644 --- a/apps/collections/pkg/apis/collections/v1alpha1/stars.go +++ b/apps/collections/pkg/apis/collections/v1alpha1/stars.go @@ -8,9 +8,8 @@ import ( func (stars *StarsSpec) Add(group, kind, name string) { for i, r := range stars.Resource { if r.Group == group && r.Kind == kind { - r.Names = append(r.Names, name) - slices.Sort(r.Names) - stars.Resource[i].Names = slices.Compact(r.Names) + stars.Resource[i].Names = append(r.Names, name) + stars.Normalize() return } } @@ -46,8 +45,15 @@ func (stars *StarsSpec) Normalize() { resources := make([]StarsResource, 0, len(stars.Resource)) for _, r := range stars.Resource { if len(r.Names) > 0 { - slices.Sort(r.Names) - r.Names = slices.Compact(r.Names) // removes any duplicates + unique := make([]string, 0, len(r.Names)) + found := make(map[string]bool, len(r.Names)) + for _, name := range r.Names { + if !found[name] { + unique = append(unique, name) + found[name] = true + } + } + r.Names = unique resources = append(resources, r) } } diff --git a/apps/collections/pkg/apis/collections/v1alpha1/stars_test.go b/apps/collections/pkg/apis/collections/v1alpha1/stars_test.go index 5713b07a751..d4421fae614 100644 --- a/apps/collections/pkg/apis/collections/v1alpha1/stars_test.go +++ b/apps/collections/pkg/apis/collections/v1alpha1/stars_test.go @@ -39,7 +39,7 @@ func TestStarsWrite(t *testing.T) { Resource: []StarsResource{{ Group: "g", Kind: "k", - Names: []string{"a", "b", "c", "x"}, // added "b" (and sorted) + Names: []string{"a", "b", "x", "c"}, // added c to the end }}, }, }, { diff --git a/pkg/registry/apis/collections/legacy/stars.go b/pkg/registry/apis/collections/legacy/stars.go index 8c7d9e711ea..39f7a9c088f 100644 --- a/pkg/registry/apis/collections/legacy/stars.go +++ b/pkg/registry/apis/collections/legacy/stars.go @@ -204,11 +204,14 @@ func (s *DashboardStarsStorage) write(ctx context.Context, obj *collections.Star previous[v] = true } } - for _, dashboard := range stars { + for idx, dashboard := range stars { if previous[dashboard] { delete(previous, dashboard) continue // nothing needed } + if idx > 0 { + time.Sleep(75 * time.Millisecond) // values are ordered by update time; this keeps the order predictable + } err = s.stars.Add(ctx, &star.StarDashboardCommand{ UserID: user.ID, OrgID: user.OrgID, diff --git a/pkg/registry/apis/collections/register.go b/pkg/registry/apis/collections/register.go index 9be4412c377..699d3bbe672 100644 --- a/pkg/registry/apis/collections/register.go +++ b/pkg/registry/apis/collections/register.go @@ -46,12 +46,6 @@ func RegisterAPIService( users user.Service, apiregistration builder.APIRegistrar, ) *APIBuilder { - // Requires development settings and clearly experimental - //nolint:staticcheck // not yet migrated to OpenFeature - if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { - return nil - } - sql := legacy.NewLegacySQL(legacysql.NewDatabaseProvider(db)) builder := &APIBuilder{ authorizer: &utils.AuthorizeFromName{ diff --git a/pkg/tests/apis/collections/stars_test.go b/pkg/tests/apis/collections/stars_test.go index fe841fb4851..573da05a00b 100644 --- a/pkg/tests/apis/collections/stars_test.go +++ b/pkg/tests/apis/collections/stars_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "slices" "testing" "github.com/stretchr/testify/require" @@ -160,7 +161,7 @@ func TestIntegrationStars(t *testing.T) { { "group": "dashboard.grafana.app", "kind": "Dashboard", - "names": []string{"test-2", "aaa", "bbb"}, + "names": []string{"test-2", "aaa", "aaa", "bbb"}, }, }, }, @@ -174,13 +175,17 @@ func TestIntegrationStars(t *testing.T) { require.Equal(t, "dashboard.grafana.app", resources[0].Group) require.Equal(t, "Dashboard", resources[0].Kind) require.ElementsMatch(t, - []string{"aaa", "bbb", "test-2"}, // NOTE 2 stays, 3 removed, added aaa+bbb (and sorted!) + []string{"test-2", "aaa", "bbb"}, // keeps the requested order, removing duplicates resources[0].Names) rspObj, err = starsClient.Resource.Get(ctx, "user-"+starsClient.Args.User.Identity.GetIdentifier(), metav1.GetOptions{}) require.NoError(t, err) after = typed(t, rspObj, &collections.Stars{}) + + // FIXME: when we remove legacy support this should not sort! + slices.Sort(after.Spec.Resource[0].Names) + jj, err := json.MarshalIndent(after.Spec, "", " ") require.NoError(t, err) require.JSONEq(t, `{ From 5ca221743f714c591d99b54e825a345f7758c181 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Thu, 11 Dec 2025 00:02:52 -0700 Subject: [PATCH 003/297] Dashboards: Prevent query for ID 0; improve logging (#115120) --- pkg/services/dashboards/service/dashboard_service.go | 7 ++++++- pkg/services/dashboards/service/dashboard_service_test.go | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index b63b4a40f96..4c3bf3fea2f 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -1305,6 +1305,11 @@ func (dr *DashboardServiceImpl) GetDashboardUIDByID(ctx context.Context, query * if err != nil { return nil, err } + + if query.ID <= 0 { + return nil, dashboards.ErrDashboardNotFound + } + result, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{ OrgId: requester.GetOrgID(), DashboardIds: []int64{query.ID}, @@ -1316,7 +1321,7 @@ func (dr *DashboardServiceImpl) GetDashboardUIDByID(ctx context.Context, query * if len(result) == 0 { return nil, dashboards.ErrDashboardNotFound } else if len(result) > 1 { - return nil, fmt.Errorf("unexpected number of dashboards found: %d. desired: 1", len(result)) + return nil, fmt.Errorf("unexpected number of dashboards for id %d. found: %d. desired: 1", query.ID, len(result)) } return &dashboards.DashboardRef{UID: result[0].UID, Slug: result[0].Slug, FolderUID: result[0].FolderUID}, nil diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index 35ac47afd9a..dafb925adb5 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -1663,6 +1663,13 @@ func TestGetDashboardUIDByID(t *testing.T) { require.NoError(t, err) require.Equal(t, expectedResult, result) k8sCliMock.AssertExpectations(t) + + // 0 should return error + _, err = service.GetDashboardUIDByID(ctx, &dashboards.GetDashboardRefByIDQuery{ + ID: 0, + }) + require.Error(t, err) + require.Equal(t, dashboards.ErrDashboardNotFound, err) } func TestUnstructuredToLegacyDashboard(t *testing.T) { From cedf08c9ce95883d7e0f84bd0a6c34147ee4f656 Mon Sep 17 00:00:00 2001 From: Victor Marin Date: Thu, 11 Dec 2025 09:03:38 +0200 Subject: [PATCH 004/297] DashboardDS: Fix datasource annotations not hiding on toggle hide (#115024) * fix dashboard datasource annotations not hiding on toggle hide * cleanup --- .../datasource/dashboard/datasource.test.ts | 117 ++++++++++++++++++ .../datasource/dashboard/datasource.ts | 57 ++++----- 2 files changed, 146 insertions(+), 28 deletions(-) diff --git a/public/app/plugins/datasource/dashboard/datasource.test.ts b/public/app/plugins/datasource/dashboard/datasource.test.ts index 2dcb17c63db..0491dfcd798 100644 --- a/public/app/plugins/datasource/dashboard/datasource.test.ts +++ b/public/app/plugins/datasource/dashboard/datasource.test.ts @@ -10,6 +10,7 @@ import { FieldType, DataFrame, AdHocVariableFilter, + DataTopic, } from '@grafana/data'; import { getPanelPlugin } from '@grafana/data/test'; import { setPluginImportUtils } from '@grafana/runtime'; @@ -774,8 +775,124 @@ describe('DashboardDatasource', () => { }); }); }); + + describe('Annotation Handling', () => { + it('should NOT include annotations from source panel in regular query response', async () => { + const { observable } = setupWithAnnotations({ refId: 'A', panelId: 1 }); + + let rsp: DataQueryResponse | undefined; + observable.subscribe({ next: (data) => (rsp = data) }); + + // Should only have series data, no annotations + expect(rsp?.data.length).toBe(1); + expect(rsp?.data[0].fields[0].values).toEqual([1, 2, 3]); + + // Verify no annotation frames are included + const annotationFrames = rsp?.data.filter((frame) => frame.meta?.dataTopic === DataTopic.Annotations); + expect(annotationFrames?.length).toBe(0); + }); + + it('should return annotations as series when query topic is DataTopic.Annotations', async () => { + const { observable } = setupWithAnnotations({ refId: 'A', panelId: 1, topic: DataTopic.Annotations }); + + let rsp: DataQueryResponse | undefined; + observable.subscribe({ next: (data) => (rsp = data) }); + + // Should return annotation data as series (with dataTopic changed to Series) + expect(rsp?.data.length).toBe(1); + expect(rsp?.data[0].name).toBe('Test Annotation'); + // The dataTopic should be changed to Series when querying for annotations + expect(rsp?.data[0].meta?.dataTopic).toBe(DataTopic.Series); + }); + + it('should not leak annotations when source panel has annotations and toggle is off', async () => { + // This test ensures that when annotations are toggled off at the dashboard level, + // DashboardDS panels don't continue showing them from the source panel's cached data + const { observable } = setupWithAnnotations({ refId: 'A', panelId: 1 }); + + let rsp: DataQueryResponse | undefined; + observable.subscribe({ next: (data) => (rsp = data) }); + + // Verify that annotations from source panel are NOT included in response + // This is critical for annotation toggle to work correctly on DashboardDS panels + const hasAnnotations = rsp?.data.some( + (frame) => frame.meta?.dataTopic === DataTopic.Annotations || frame.name === 'Test Annotation' + ); + expect(hasAnnotations).toBe(false); + }); + + it('should only return series data even when source has both series and annotations', async () => { + const { observable } = setupWithAnnotations({ refId: 'A', panelId: 1 }); + + let rsp: DataQueryResponse | undefined; + observable.subscribe({ next: (data) => (rsp = data) }); + + // All returned frames should be series data, not annotations + rsp?.data.forEach((frame) => { + expect(frame.meta?.dataTopic).not.toBe(DataTopic.Annotations); + }); + + // Should have the series data from the source panel + expect(rsp?.data[0].fields[0].values).toEqual([1, 2, 3]); + }); + }); }); +function setupWithAnnotations(query: DashboardQuery, requestId?: string) { + const annotationFrame: DataFrame = { + name: 'Test Annotation', + fields: [ + { name: 'time', type: FieldType.time, values: [1000, 2000], config: {} }, + { name: 'text', type: FieldType.string, values: ['Annotation 1', 'Annotation 2'], config: {} }, + ], + length: 2, + meta: { + dataTopic: DataTopic.Annotations, + }, + }; + + const sourceData = new SceneDataTransformer({ + $data: new SceneDataNode({ + data: { + series: [arrayToDataFrame([1, 2, 3])], + annotations: [annotationFrame], + state: LoadingState.Done, + timeRange: getDefaultTimeRange(), + }, + }), + transformations: [], + }); + + const scene = new SceneFlexLayout({ + children: [ + new SceneFlexItem({ + body: new VizPanel({ + key: getVizPanelKeyForPanelId(1), + $data: sourceData, + }), + }), + ], + }); + + const ds = new DashboardDatasource({} as DataSourceInstanceSettings); + + const observable = ds.query({ + timezone: 'utc', + targets: [query], + requestId: requestId ?? '', + interval: '', + intervalMs: 0, + range: getDefaultTimeRange(), + scopedVars: { + __sceneObject: new SafeSerializableSceneObject(scene), + }, + app: '', + startTime: 0, + }); + + return { observable, sourceData }; +} + function setup(query: DashboardQuery, requestId?: string) { const sourceData = new SceneDataTransformer({ $data: new SceneDataNode({ diff --git a/public/app/plugins/datasource/dashboard/datasource.ts b/public/app/plugins/datasource/dashboard/datasource.ts index 34a1781623f..63fefd10bb1 100644 --- a/public/app/plugins/datasource/dashboard/datasource.ts +++ b/public/app/plugins/datasource/dashboard/datasource.ts @@ -122,8 +122,9 @@ export class DashboardDatasource extends DataSourceApi { query: DashboardQuery, filters: AdHocVariableFilter[] ): DataFrame[] { - const annotations = data.annotations ?? []; + // When querying for annotations topic, return the source panel's annotations as series data if (query.topic === DataTopic.Annotations) { + const annotations = data.annotations ?? []; return annotations.map((frame) => ({ ...frame, meta: { @@ -131,34 +132,34 @@ export class DashboardDatasource extends DataSourceApi { dataTopic: DataTopic.Series, }, })); - } else { - const series = data.series.map((s) => { - return { - ...s, - fields: s.fields.map((field: Field) => ({ - ...field, - config: { - ...field.config, - // Enable AdHoc filtering for string and numeric fields only when per-panel setting is enabled - filterable: query.adHocFiltersEnabled - ? field.type === FieldType.string || field.type === FieldType.number - : field.config.filterable, - }, - state: { - ...field.state, - }, - })), - }; - }); - - if (!query.adHocFiltersEnabled || filters.length === 0) { - return [...series, ...annotations]; - } - - // Apply AdHoc filters to series data - const filteredSeries = series.map((frame) => this.applyAdHocFilters(frame, filters)); - return [...filteredSeries, ...annotations]; } + + // For regular queries, only return series data + const series = data.series.map((s) => { + return { + ...s, + fields: s.fields.map((field: Field) => ({ + ...field, + config: { + ...field.config, + // Enable AdHoc filtering for string and numeric fields only when per-panel setting is enabled + filterable: query.adHocFiltersEnabled + ? field.type === FieldType.string || field.type === FieldType.number + : field.config.filterable, + }, + state: { + ...field.state, + }, + })), + }; + }); + + if (!query.adHocFiltersEnabled || filters.length === 0) { + return series; + } + + // Apply AdHoc filters to series data + return series.map((frame) => this.applyAdHocFilters(frame, filters)); } /** From d686a49cf78c2e253a9390cb99699c183858a5ee Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 11 Dec 2025 10:33:13 +0300 Subject: [PATCH 005/297] Preferences: Enable preferences APIserver (#115128) --- pkg/registry/apis/preferences/register.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/registry/apis/preferences/register.go b/pkg/registry/apis/preferences/register.go index 8964b0e0cd7..e0e6ff947fc 100644 --- a/pkg/registry/apis/preferences/register.go +++ b/pkg/registry/apis/preferences/register.go @@ -42,12 +42,6 @@ func RegisterAPIService( users user.Service, apiregistration builder.APIRegistrar, ) *APIBuilder { - // Requires development settings and clearly experimental - //nolint:staticcheck // not yet migrated to OpenFeature - if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { - return nil - } - sql := legacy.NewLegacySQL(legacysql.NewDatabaseProvider(db)) builder := &APIBuilder{ merger: newMerger(cfg, sql), From 33c5cbf4de57516c82fee5eff938dd824de7293c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 11 Dec 2025 08:54:50 +0100 Subject: [PATCH 006/297] Dashboards: Update edit button and share button (#115093) * Dashboards: Update edit button and share button * update translations --- .../edit-pane/DashboardEditPaneSplitter.tsx | 5 - .../actions/EditDashboardSwitch.tsx | 26 +++-- .../actions/ShareDashboardButton.tsx | 2 +- .../actions/ShareExportDashboardButton.tsx | 19 +-- .../new-toolbar/actions/ToolbarSwitch.tsx | 108 ------------------ public/locales/en-US/grafana.json | 14 +-- 6 files changed, 36 insertions(+), 138 deletions(-) delete mode 100644 public/app/features/dashboard-scene/scene/new-toolbar/actions/ToolbarSwitch.tsx diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx index 091e4805bae..e32dbcd090d 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx @@ -191,11 +191,6 @@ function getStyles(theme: GrafanaTheme2, headerHeight: number) { // Because the edit pane splitter handle area adds padding we can reduce it here paddingRight: theme.spacing(1), }), - editPane: css({ - flexDirection: 'column', - // borderLeft: `1px solid ${theme.colors.border.weak}`, - // background: theme.colors.background.primary, - }), controlsWrapperSticky: css({ [theme.breakpoints.up('md')]: { position: 'sticky', diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/EditDashboardSwitch.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/EditDashboardSwitch.tsx index 8389e11dfe4..777c3892860 100644 --- a/public/app/features/dashboard-scene/scene/new-toolbar/actions/EditDashboardSwitch.tsx +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/EditDashboardSwitch.tsx @@ -1,22 +1,26 @@ import { selectors } from '@grafana/e2e-selectors'; import { t } from '@grafana/i18n'; +import { Button } from '@grafana/ui'; import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions'; import { trackDashboardSceneEditButtonClicked } from 'app/features/dashboard-scene/utils/tracking'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; import { ToolbarActionProps } from '../types'; -import { ToolbarSwitch } from './ToolbarSwitch'; - export const EditDashboardSwitch = ({ dashboard }: ToolbarActionProps) => { + const tooltip = dashboard.state.isEditing + ? t('dashboard.toolbar.edit-button.exit-tooltip', 'Exit edit mode') + : t('dashboard.toolbar.edit-button.enter-tooltip', 'Enter edit mode'); + + if (playlistSrv.state.isPlaying) { + return null; + } + return ( - { evt.preventDefault(); evt.stopPropagation(); @@ -29,6 +33,10 @@ export const EditDashboardSwitch = ({ dashboard }: ToolbarActionProps) => { dashboard.exitEditMode({ skipConfirm: false }); } }} - /> + > + {dashboard.state.isEditing + ? t('dashboard.toolbar.edit-button.exit', 'Exit edit') + : t('dashboard.toolbar.edit-button.enter', 'Edit')} + ); }; diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/ShareDashboardButton.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ShareDashboardButton.tsx index 66eec343ea3..2bb4aa0d7ee 100644 --- a/public/app/features/dashboard-scene/scene/new-toolbar/actions/ShareDashboardButton.tsx +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ShareDashboardButton.tsx @@ -34,7 +34,7 @@ export const ShareDashboardButton = ({ dashboard }: ToolbarActionProps) => { arrowLabel={t('dashboard.toolbar.new.share.arrow', 'Share')} arrowTestId={newShareButtonSelector.arrowMenu} dashboard={dashboard} - variant={!dashboard.state.isEditing ? 'primary' : 'secondary'} + variant={!dashboard.state.isEditing ? 'primary' : 'canvas'} /> ); }; diff --git a/public/app/features/dashboard-scene/scene/new-toolbar/actions/ShareExportDashboardButton.tsx b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ShareExportDashboardButton.tsx index 8783cd89751..ad9d28c11a0 100644 --- a/public/app/features/dashboard-scene/scene/new-toolbar/actions/ShareExportDashboardButton.tsx +++ b/public/app/features/dashboard-scene/scene/new-toolbar/actions/ShareExportDashboardButton.tsx @@ -1,7 +1,7 @@ import { ReactElement, useState } from 'react'; import { t } from '@grafana/i18n'; -import { Button, ButtonGroup, Dropdown } from '@grafana/ui'; +import { ButtonGroup, Dropdown, ToolbarButton } from '@grafana/ui'; import { appEvents } from 'app/core/app_events'; import { ShowConfirmModalEvent } from 'app/types/events'; @@ -17,7 +17,7 @@ interface Props extends ToolbarActionProps { onButtonClick?: () => void; arrowLabel: string; arrowTestId: string; - variant?: 'primary' | 'secondary'; + variant?: 'primary' | 'canvas'; } export const ShareExportDashboardButton = ({ @@ -31,7 +31,7 @@ export const ShareExportDashboardButton = ({ onButtonClick, arrowLabel, arrowTestId, - variant = 'secondary', + variant = 'canvas', }: Props) => { const [isOpen, setIsOpen] = useState(false); @@ -60,9 +60,15 @@ export const ShareExportDashboardButton = ({ } }} > - + - - - ); -}; - -const getStyles = (theme: GrafanaTheme2) => ({ - container: css({ - border: `1px solid ${theme.components.input.borderColor}`, - padding: theme.spacing(0.5), - backgroundColor: theme.components.input.background, - borderRadius: theme.shape.radius.default, - width: theme.spacing(6.5), - height: theme.spacing(theme.components.height.md), - cursor: 'pointer', - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - - [theme.transitions.handleMotion('no-preference', 'reduce')]: { - transition: 'all 0.2s ease-in-out', - }, - - '&:hover': { - borderColor: theme.components.input.borderHover, - }, - }), - containerChecked: css({ - backgroundColor: theme.colors.primary.main, - borderColor: 'transparent', - - '&:hover': { - backgroundColor: theme.colors.primary.shade, - borderColor: 'transparent', - }, - }), - containerDisabled: css({ - cursor: 'initial', - background: theme.colors.action.disabledBackground, - borderColor: theme.colors.border.weak, - }), - box: css({ - background: theme.colors.background.primary, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - width: theme.spacing(3.5), - height: '100%', - transform: 'translateX(0)', - position: 'relative', - borderRadius: styleMixins.getInternalRadius(theme, 2), - border: `1px solid ${theme.colors.border.weak}`, - - [theme.transitions.handleMotion('no-preference', 'reduce')]: { - transition: 'all 0.2s ease-in-out', - }, - }), - boxChecked: css({ - transform: `translateX(calc(100% - 14px))`, - borderColor: 'transparent', - }), -}); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 2e23a60cff9..3ced02e0c08 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -5463,6 +5463,12 @@ "label": "Edit", "tooltip": "Enter edit mode" }, + "edit-button": { + "enter": "Edit", + "enter-tooltip": "Enter edit mode", + "exit": "Exit edit", + "exit-tooltip": "Exit edit mode" + }, "enter-edit-mode": { "label": "Make editable", "tooltip": "This dashboard was marked as read only" @@ -5484,14 +5490,6 @@ "edit-dashboard-v2-schema": { "tooltip": "Edit dashboard v2 schema" }, - "edit-toggle": { - "enter": { - "label": "Enter edit mode" - }, - "exit": { - "label": "Exit edit mode" - } - }, "enter-edit-mode": { "label": "Make editable", "tooltip": "This dashboard was marked as read only" From 8aab6302c549314561396ef798282048d2555bfd Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Thu, 11 Dec 2025 09:01:31 +0100 Subject: [PATCH 007/297] Fix conversion error shallowed and normalize conversion status (#115086) * Fix the conversion shallowed error and normalize the conversion status * Add unit tests to ensure all permutations data loss detection * Fix counting issue --- .../conversion_data_loss_detection.go | 92 +- .../conversion_data_loss_detection_test.go | 2256 +++++++++++++++-- ...mig-v10.table_thresholds.v42.v2alpha1.json | 7 +- ...-mig-v10.table_thresholds.v42.v2beta1.json | 7 +- ...-mig-v11.no-op-migration.v42.v2alpha1.json | 7 +- ...1-mig-v11.no-op-migration.v42.v2beta1.json | 7 +- ...g-v12.template-variables.v42.v2alpha1.json | 7 +- ...ig-v12.template-variables.v42.v2beta1.json | 7 +- ...mig-v13.graph_thresholds.v42.v2alpha1.json | 7 +- ...-mig-v13.graph_thresholds.v42.v2beta1.json | 7 +- ...v13.minimal_graph_config.v42.v2alpha1.json | 7 +- ...-v13.minimal_graph_config.v42.v2beta1.json | 7 +- ...osshair_to_graph_tooltip.v42.v2alpha1.json | 7 +- ...rosshair_to_graph_tooltip.v42.v2beta1.json | 7 +- ....mimir_rollout_debugging.v42.v2alpha1.json | 7 +- ...5.mimir_rollout_debugging.v42.v2beta1.json | 7 +- ...-mig-v15.no-op-migration.v42.v2alpha1.json | 7 +- ...1-mig-v15.no-op-migration.v42.v2beta1.json | 7 +- ...ty-rows-and-panels-array.v42.v2alpha1.json | 7 +- ...pty-rows-and-panels-array.v42.v2beta1.json | 7 +- ...-v16.grid_layout_upgrade.v42.v2alpha1.json | 7 +- ...g-v16.grid_layout_upgrade.v42.v2beta1.json | 7 +- ...1-mig-v16.span_zero_demo.v42.v2alpha1.json | 7 +- ...a1-mig-v16.span_zero_demo.v42.v2beta1.json | 7 +- ...v17.minspan_to_maxperrow.v42.v2alpha1.json | 7 +- ...-v17.minspan_to_maxperrow.v42.v2beta1.json | 7 +- ...a1-mig-v18.gauge_options.v42.v2alpha1.json | 7 +- ...ta1-mig-v18.gauge_options.v42.v2beta1.json | 7 +- ...eta1-mig-v19.panel_links.v42.v2alpha1.json | 7 +- ...beta1-mig-v19.panel_links.v42.v2beta1.json | 7 +- ...g-v2.panels-and-services.v42.v2alpha1.json | 7 +- ...ig-v2.panels-and-services.v42.v2beta1.json | 7 +- ...20.variable_syntax_links.v42.v2alpha1.json | 7 +- ...v20.variable_syntax_links.v42.v2beta1.json | 7 +- ...ta_links_series_to_field.v42.v2alpha1.json | 7 +- ...ata_links_series_to_field.v42.v2beta1.json | 7 +- ...ig-v22.table_panel_align.v42.v2alpha1.json | 7 +- ...mig-v22.table_panel_align.v42.v2beta1.json | 7 +- ...multi_variable_alignment.v42.v2alpha1.json | 7 +- ....multi_variable_alignment.v42.v2beta1.json | 7 +- ...a1-mig-v24.table-angular.v42.v2alpha1.json | 7 +- ...ta1-mig-v24.table-angular.v42.v2beta1.json | 7 +- ...-mig-v25.no-op-migration.v42.v2alpha1.json | 7 +- ...1-mig-v25.no-op-migration.v42.v2beta1.json | 7 +- ...a1-mig-v26.text2_to_text.v42.v2alpha1.json | 7 +- ...ta1-mig-v26.text2_to_text.v42.v2beta1.json | 7 +- ...ls_and_constant_variable.v42.v2alpha1.json | 7 +- ...els_and_constant_variable.v42.v2beta1.json | 7 +- ...move_variable_properties.v42.v2alpha1.json | 7 +- ...emove_variable_properties.v42.v2beta1.json | 7 +- ..._and_variable_properties.v42.v2alpha1.json | 7 +- ...t_and_variable_properties.v42.v2beta1.json | 7 +- ...v28.singlestat_migration.v42.v2alpha1.json | 7 +- ...-v28.singlestat_migration.v42.v2beta1.json | 7 +- ...bles_refresh_and_options.v42.v2alpha1.json | 7 +- ...ables_refresh_and_options.v42.v2beta1.json | 7 +- .../v1beta1-mig-v3.no-op.v42.v2alpha1.json | 7 +- .../v1beta1-mig-v3.no-op.v42.v2beta1.json | 7 +- ...ings_and_tooltip_options.v42.v2alpha1.json | 7 +- ...pings_and_tooltip_options.v42.v2beta1.json | 7 +- ...1.labels_to_fields_merge.v42.v2alpha1.json | 7 +- ...31.labels_to_fields_merge.v42.v2beta1.json | 7 +- ...-mig-v32.no_op_migration.v42.v2alpha1.json | 7 +- ...1-mig-v32.no_op_migration.v42.v2beta1.json | 7 +- ...v33.panel_ds_name_to_ref.v42.v2alpha1.json | 7 +- ...-v33.panel_ds_name_to_ref.v42.v2beta1.json | 7 +- ...ultiple_stats_cloudwatch.v42.v2alpha1.json | 7 +- ...multiple_stats_cloudwatch.v42.v2beta1.json | 7 +- ...ensure_x_axis_visibility.v42.v2alpha1.json | 7 +- ....ensure_x_axis_visibility.v42.v2beta1.json | 7 +- ...1-mig-v36.ds_name_to_ref.v42.v2alpha1.json | 7 +- ...a1-mig-v36.ds_name_to_ref.v42.v2beta1.json | 7 +- ...v37.legend_normalization.v42.v2alpha1.json | 7 +- ...-v37.legend_normalization.v42.v2beta1.json | 7 +- ...isplaymode_comprehensive.v42.v2alpha1.json | 7 +- ...displaymode_comprehensive.v42.v2beta1.json | 7 +- ...eries_table_display_mode.v42.v2alpha1.json | 7 +- ...series_table_display_mode.v42.v2beta1.json | 7 +- ...ansform_timeseries_table.v42.v2alpha1.json | 7 +- ...ransform_timeseries_table.v42.v2beta1.json | 7 +- .../v1beta1-mig-v4.no-op.v42.v2alpha1.json | 7 +- .../v1beta1-mig-v4.no-op.v42.v2beta1.json | 7 +- ...v40.refresh_empty_string.v42.v2alpha1.json | 7 +- ...-v40.refresh_empty_string.v42.v2beta1.json | 7 +- ...a1-mig-v40.refresh_false.v42.v2alpha1.json | 7 +- ...ta1-mig-v40.refresh_false.v42.v2beta1.json | 7 +- ...-mig-v40.refresh_not_set.v42.v2alpha1.json | 7 +- ...1-mig-v40.refresh_not_set.v42.v2beta1.json | 7 +- ...-mig-v40.refresh_numeric.v42.v2alpha1.json | 7 +- ...1-mig-v40.refresh_numeric.v42.v2beta1.json | 7 +- ...1-mig-v40.refresh_string.v42.v2alpha1.json | 7 +- ...a1-mig-v40.refresh_string.v42.v2beta1.json | 7 +- ...ta1-mig-v40.refresh_true.v42.v2alpha1.json | 7 +- ...eta1-mig-v40.refresh_true.v42.v2beta1.json | 7 +- ...1-mig-v41.no_time_picker.v42.v2alpha1.json | 7 +- ...a1-mig-v41.no_time_picker.v42.v2beta1.json | 7 +- ...e_picker_no_time_options.v42.v2alpha1.json | 7 +- ...me_picker_no_time_options.v42.v2beta1.json | 7 +- ...time_picker_time_options.v42.v2alpha1.json | 7 +- ....time_picker_time_options.v42.v2beta1.json | 7 +- ...beta1-mig-v42.harky_must.v42.v2alpha1.json | 7 +- ...1beta1-mig-v42.harky_must.v42.v2beta1.json | 7 +- ...mig-v42.hidefrom_tooltip.v42.v2alpha1.json | 7 +- ...-mig-v42.hidefrom_tooltip.v42.v2beta1.json | 7 +- .../v1beta1-mig-v5.no-op.v42.v2alpha1.json | 7 +- .../v1beta1-mig-v5.no-op.v42.v2beta1.json | 7 +- ...pulldowns_and_templating.v42.v2alpha1.json | 7 +- ....pulldowns_and_templating.v42.v2beta1.json | 7 +- ...1beta1-mig-v7.timepicker.v42.v2alpha1.json | 7 +- ...v1beta1-mig-v7.timepicker.v42.v2beta1.json | 7 +- ...a1-mig-v8.influxdb_query.v42.v2alpha1.json | 7 +- ...ta1-mig-v8.influxdb_query.v42.v2beta1.json | 7 +- .../v1beta1-mig-v9.no-op.v42.v2alpha1.json | 7 +- .../v1beta1-mig-v9.no-op.v42.v2beta1.json | 7 +- ...ha1.annotation-filtering.v42.v2alpha1.json | 7 +- ...pha1.annotation-filtering.v42.v2beta1.json | 7 +- ...1.multi-lane-annotations.v42.v2alpha1.json | 7 +- ...a1.multi-lane-annotations.v42.v2beta1.json | 7 +- ...a1.elasticsearch_complex.v42.v2alpha1.json | 7 +- ...ha1.elasticsearch_complex.v42.v2beta1.json | 7 +- ....elasticsearch_migration.v42.v2alpha1.json | 7 +- ...1.elasticsearch_migration.v42.v2beta1.json | 7 +- ...ha1.elasticsearch_simple.v42.v2alpha1.json | 7 +- ...pha1.elasticsearch_simple.v42.v2beta1.json | 7 +- .../v0alpha1.influxdb-logs.v42.v2alpha1.json | 7 +- .../v0alpha1.influxdb-logs.v42.v2beta1.json | 7 +- ...lpha1.influxdb-templated.v42.v2alpha1.json | 7 +- ...alpha1.influxdb-templated.v42.v2beta1.json | 7 +- .../v0alpha1.loki_fakedata.v42.v2alpha1.json | 7 +- .../v0alpha1.loki_fakedata.v42.v2beta1.json | 7 +- ...ha1.loki_query_splitting.v42.v2alpha1.json | 7 +- ...pha1.loki_query_splitting.v42.v2beta1.json | 7 +- .../v0alpha1.mssql_fakedata.v42.v2alpha1.json | 7 +- .../v0alpha1.mssql_fakedata.v42.v2beta1.json | 7 +- .../v0alpha1.mssql_unittest.v42.v2alpha1.json | 7 +- .../v0alpha1.mssql_unittest.v42.v2beta1.json | 7 +- .../v0alpha1.mysql_fakedata.v42.v2alpha1.json | 7 +- .../v0alpha1.mysql_fakedata.v42.v2beta1.json | 7 +- .../v0alpha1.mysql_unittest.v42.v2alpha1.json | 7 +- .../v0alpha1.mysql_unittest.v42.v2beta1.json | 7 +- .../v0alpha1.opentsdb.v42.v2alpha1.json | 7 +- .../v0alpha1.opentsdb.v42.v2beta1.json | 7 +- .../v0alpha1.opentsdb_v23.v42.v2alpha1.json | 7 +- .../v0alpha1.opentsdb_v23.v42.v2beta1.json | 7 +- ...alpha1.postgres_fakedata.v42.v2alpha1.json | 7 +- ...0alpha1.postgres_fakedata.v42.v2beta1.json | 7 +- ...alpha1.postgres_unittest.v42.v2alpha1.json | 7 +- ...0alpha1.postgres_unittest.v42.v2beta1.json | 7 +- ...v0alpha1.bar-gauge-demo2.v42.v2alpha1.json | 7 +- .../v0alpha1.bar-gauge-demo2.v42.v2beta1.json | 7 +- .../v0alpha1.demo1.v42.v2alpha1.json | 7 +- .../v0alpha1.demo1.v42.v2beta1.json | 7 +- ...pha1.new_features_in_v74.v42.v2alpha1.json | 7 +- ...lpha1.new_features_in_v74.v42.v2beta1.json | 7 +- ...lpha1.new_features_in_v8.v42.v2alpha1.json | 7 +- ...alpha1.new_features_in_v8.v42.v2beta1.json | 7 +- ...1.Repeating-Kitchen-Sink.v42.v2alpha1.json | 7 +- ...a1.Repeating-Kitchen-Sink.v42.v2beta1.json | 7 +- ...ing-a-panel-horizontally.v42.v2alpha1.json | 7 +- ...ting-a-panel-horizontally.v42.v2beta1.json | 7 +- ...ating-a-panel-vertically.v42.v2alpha1.json | 7 +- ...eating-a-panel-vertically.v42.v2beta1.json | 7 +- ...peating-horizontal-panel.v42.v2alpha1.json | 7 +- ...epeating-horizontal-panel.v42.v2beta1.json | 7 +- ...repeating-vertical-panel.v42.v2alpha1.json | 7 +- ...-repeating-vertical-panel.v42.v2beta1.json | 7 +- ...1.Repeating-an-empty-row.v42.v2alpha1.json | 7 +- ...a1.Repeating-an-empty-row.v42.v2beta1.json | 7 +- ....link-onclick-extensions.v42.v2alpha1.json | 7 +- ...1.link-onclick-extensions.v42.v2beta1.json | 7 +- ...ha1.link-path-extensions.v42.v2alpha1.json | 7 +- ...pha1.link-path-extensions.v42.v2beta1.json | 7 +- ...v0alpha1.datadata-macros.v42.v2alpha1.json | 7 +- .../v0alpha1.datadata-macros.v42.v2beta1.json | 7 +- ...iables-and-interpolation.v42.v2alpha1.json | 7 +- ...riables-and-interpolation.v42.v2beta1.json | 7 +- ...oard-links-and-variables.v42.v2alpha1.json | 7 +- ...board-links-and-variables.v42.v2beta1.json | 7 +- ...plating-repeating-panels.v42.v2alpha1.json | 7 +- ...mplating-repeating-panels.v42.v2beta1.json | 7 +- ...emplating-repeating-rows.v42.v2alpha1.json | 7 +- ...templating-repeating-rows.v42.v2beta1.json | 7 +- ...ng-textbox-e2e-scenarios.v42.v2alpha1.json | 7 +- ...ing-textbox-e2e-scenarios.v42.v2beta1.json | 7 +- ...lpha1.testdata-datalinks.v42.v2alpha1.json | 7 +- ...alpha1.testdata-datalinks.v42.v2beta1.json | 7 +- ...sted-variables-drilldown.v42.v2alpha1.json | 7 +- ...ested-variables-drilldown.v42.v2beta1.json | 7 +- ...estdata-nested-variables.v42.v2alpha1.json | 7 +- ...testdata-nested-variables.v42.v2beta1.json | 7 +- ...ata-test-variable-output.v42.v2alpha1.json | 7 +- ...data-test-variable-output.v42.v2beta1.json | 7 +- ...stdata-variables-textbox.v42.v2alpha1.json | 7 +- ...estdata-variables-textbox.v42.v2beta1.json | 7 +- ...at-update-on-time-change.v42.v2alpha1.json | 7 +- ...hat-update-on-time-change.v42.v2beta1.json | 7 +- ...pha1.live-flakey-refresh.v42.v2alpha1.json | 7 +- ...lpha1.live-flakey-refresh.v42.v2beta1.json | 7 +- .../v0alpha1.live-flakey.v42.v2alpha1.json | 7 +- .../v0alpha1.live-flakey.v42.v2beta1.json | 7 +- .../v0alpha1.live-publish.v42.v2alpha1.json | 7 +- .../v0alpha1.live-publish.v42.v2beta1.json | 7 +- .../v0alpha1.live-streams.v42.v2alpha1.json | 7 +- .../v0alpha1.live-streams.v42.v2beta1.json | 7 +- .../v0alpha1.migrations.v42.v2alpha1.json | 7 +- .../v0alpha1.migrations.v42.v2beta1.json | 7 +- ...pha1.barchart-autosizing.v42.v2alpha1.json | 7 +- ...lpha1.barchart-autosizing.v42.v2beta1.json | 7 +- ...-label-rotation-skipping.v42.v2alpha1.json | 7 +- ...t-label-rotation-skipping.v42.v2beta1.json | 7 +- ...1.barchart-series-toggle.v42.v2alpha1.json | 7 +- ...a1.barchart-series-toggle.v42.v2beta1.json | 7 +- ...hart-thresholds-mappings.v42.v2alpha1.json | 7 +- ...chart-thresholds-mappings.v42.v2beta1.json | 7 +- ...archart-tooltips-legends.v42.v2alpha1.json | 7 +- ...barchart-tooltips-legends.v42.v2beta1.json | 7 +- .../v0alpha1.bar_gauge_demo.v42.v2alpha1.json | 7 +- .../v0alpha1.bar_gauge_demo.v42.v2beta1.json | 7 +- ...a1.panel_tests_bar_gauge.v42.v2alpha1.json | 7 +- ...ha1.panel_tests_bar_gauge.v42.v2beta1.json | 7 +- ...1.panel_tests_bar_gauge2.v42.v2alpha1.json | 7 +- ...a1.panel_tests_bar_gauge2.v42.v2beta1.json | 7 +- .../v0alpha1.candlestick.v42.v2alpha1.json | 7 +- .../v0alpha1.candlestick.v42.v2beta1.json | 7 +- ...nvas-connection-examples.v42.v2alpha1.json | 7 +- ...anvas-connection-examples.v42.v2beta1.json | 7 +- ...0alpha1.canvas-datalinks.v42.v2alpha1.json | 7 +- ...v0alpha1.canvas-datalinks.v42.v2beta1.json | 7 +- ...v0alpha1.canvas-examples.v42.v2alpha1.json | 7 +- .../v0alpha1.canvas-examples.v42.v2beta1.json | 7 +- .../v0alpha1.auto_decimals.v42.v2alpha1.json | 7 +- .../v0alpha1.auto_decimals.v42.v2beta1.json | 7 +- .../v0alpha1.color_modes.v42.v2alpha1.json | 7 +- .../v0alpha1.color_modes.v42.v2beta1.json | 7 +- .../v0alpha1.lazy_loading.v42.v2alpha1.json | 7 +- .../v0alpha1.lazy_loading.v42.v2beta1.json | 7 +- .../v0alpha1.linked-viz.v42.v2alpha1.json | 7 +- .../v0alpha1.linked-viz.v42.v2beta1.json | 7 +- ...ha1.panels_without_title.v42.v2alpha1.json | 7 +- ...pha1.panels_without_title.v42.v2beta1.json | 7 +- .../v0alpha1.shared_queries.v42.v2alpha1.json | 7 +- .../v0alpha1.shared_queries.v42.v2beta1.json | 7 +- .../v0alpha1.dashlist.v42.v2alpha1.json | 7 +- .../v0alpha1.dashlist.v42.v2beta1.json | 7 +- ...1.datagrid_metric_values.v42.v2alpha1.json | 7 +- ...a1.datagrid_metric_values.v42.v2beta1.json | 7 +- ....panel_tests_flame_graph.v42.v2alpha1.json | 7 +- ...1.panel_tests_flame_graph.v42.v2beta1.json | 7 +- ...lpha1.gauge-multi-series.v42.v2alpha1.json | 7 +- ...alpha1.gauge-multi-series.v42.v2beta1.json | 7 +- .../v0alpha1.gauge_tests.v42.v2alpha1.json | 7 +- .../v0alpha1.gauge_tests.v42.v2beta1.json | 7 +- .../v0alpha1.gauge_tests_new.v42.v1beta1.json | 102 +- ...v0alpha1.gauge_tests_new.v42.v2alpha1.json | 109 +- .../v0alpha1.gauge_tests_new.v42.v2beta1.json | 109 +- ...a1.gauge_tests_old_to_new.v42.v1beta1.json | 6 +- ...1.gauge_tests_old_to_new.v42.v2alpha1.json | 13 +- ...a1.gauge_tests_old_to_new.v42.v2beta1.json | 13 +- ...lpha1.geomap-color-field.v42.v2alpha1.json | 7 +- ...alpha1.geomap-color-field.v42.v2beta1.json | 7 +- ...lpha1.geomap-photo-layer.v42.v2alpha1.json | 7 +- ...alpha1.geomap-photo-layer.v42.v2beta1.json | 7 +- ...lpha1.geomap-route-layer.v42.v2alpha1.json | 7 +- ...alpha1.geomap-route-layer.v42.v2beta1.json | 7 +- ...l-operations-transformer.v42.v2alpha1.json | 7 +- ...al-operations-transformer.v42.v2beta1.json | 7 +- .../v0alpha1.geomap-v91.v42.v2alpha1.json | 7 +- .../v0alpha1.geomap-v91.v42.v2beta1.json | 7 +- ...pha1.geomap_multi-layers.v42.v2alpha1.json | 7 +- ...lpha1.geomap_multi-layers.v42.v2beta1.json | 7 +- .../v0alpha1.panel-geomap.v42.v2alpha1.json | 7 +- .../v0alpha1.panel-geomap.v42.v2beta1.json | 7 +- ...raph-gradient-area-fills.v42.v2alpha1.json | 7 +- ...graph-gradient-area-fills.v42.v2beta1.json | 7 +- ...a1.graph-shared-tooltips.v42.v2alpha1.json | 7 +- ...ha1.graph-shared-tooltips.v42.v2beta1.json | 7 +- ...lpha1.graph-time-regions.v42.v2alpha1.json | 7 +- ...alpha1.graph-time-regions.v42.v2beta1.json | 7 +- .../v0alpha1.graph_tests.v42.v2alpha1.json | 7 +- .../v0alpha1.graph_tests.v42.v2beta1.json | 7 +- .../v0alpha1.graph_y_axis.v42.v2alpha1.json | 7 +- .../v0alpha1.graph_y_axis.v42.v2beta1.json | 7 +- ...a1.heatmap-calculate-log.v42.v2alpha1.json | 7 +- ...ha1.heatmap-calculate-log.v42.v2beta1.json | 7 +- .../v0alpha1.heatmap-legacy.v42.v2alpha1.json | 7 +- .../v0alpha1.heatmap-legacy.v42.v2beta1.json | 7 +- .../v0alpha1.heatmap-x.v42.v2alpha1.json | 7 +- .../v0alpha1.heatmap-x.v42.v2beta1.json | 7 +- ...v0alpha1.histogram_tests.v42.v2alpha1.json | 7 +- .../v0alpha1.histogram_tests.v42.v2beta1.json | 7 +- .../v0alpha1.panel-library.v42.v2alpha1.json | 7 +- .../v0alpha1.panel-library.v42.v2beta1.json | 7 +- ...pha1.panel_test_piechart.v42.v2alpha1.json | 7 +- ...lpha1.panel_test_piechart.v42.v2beta1.json | 7 +- .../v0alpha1.polystat_test.v42.v2alpha1.json | 7 +- .../v0alpha1.polystat_test.v42.v2beta1.json | 7 +- ...0alpha1.panel-stat-tests.v42.v2alpha1.json | 7 +- ...v0alpha1.panel-stat-tests.v42.v2beta1.json | 7 +- ...tory-thresholds-mappings.v42.v2alpha1.json | 7 +- ...story-thresholds-mappings.v42.v2beta1.json | 7 +- .../v0alpha1.table_footer.v42.v2alpha1.json | 7 +- .../v0alpha1.table_footer.v42.v2beta1.json | 7 +- ...lpha1.table_kitchen_sink.v42.v2alpha1.json | 7 +- ...alpha1.table_kitchen_sink.v42.v2beta1.json | 7 +- .../v0alpha1.table_markdown.v42.v2alpha1.json | 7 +- .../v0alpha1.table_markdown.v42.v2beta1.json | 7 +- ...0alpha1.table_pagination.v42.v2alpha1.json | 7 +- ...v0alpha1.table_pagination.v42.v2beta1.json | 7 +- ...ha1.table_sparkline_cell.v42.v2alpha1.json | 7 +- ...pha1.table_sparkline_cell.v42.v2beta1.json | 7 +- .../v0alpha1.table_tests.v42.v2alpha1.json | 7 +- .../v0alpha1.table_tests.v42.v2beta1.json | 7 +- ...v0alpha1.table_tests_new.v42.v2alpha1.json | 7 +- .../v0alpha1.table_tests_new.v42.v2beta1.json | 7 +- ...1.table_v12_2_migrations.v42.v2alpha1.json | 7 +- ...a1.table_v12_2_migrations.v42.v2beta1.json | 7 +- .../v0alpha1.text-options.v42.v2alpha1.json | 7 +- .../v0alpha1.text-options.v42.v2beta1.json | 7 +- ...1.timeline-align-endtime.v42.v2alpha1.json | 7 +- ...a1.timeline-align-endtime.v42.v2beta1.json | 7 +- ...eline-align-nulls-retain.v42.v2alpha1.json | 7 +- ...meline-align-nulls-retain.v42.v2beta1.json | 7 +- .../v0alpha1.timeline-demo.v42.v2alpha1.json | 7 +- .../v0alpha1.timeline-demo.v42.v2beta1.json | 7 +- .../v0alpha1.timeline-modes.v42.v2alpha1.json | 7 +- .../v0alpha1.timeline-modes.v42.v2beta1.json | 7 +- ...line-thresholds-mappings.v42.v2alpha1.json | 7 +- ...eline-thresholds-mappings.v42.v2beta1.json | 7 +- ...series-bars-high-density.v42.v2alpha1.json | 7 +- ...eseries-bars-high-density.v42.v2beta1.json | 7 +- ...s-by-value-color-schemes.v42.v2alpha1.json | 7 +- ...es-by-value-color-schemes.v42.v2beta1.json | 7 +- ...lpha1.timeseries-formats.v42.v2alpha1.json | 7 +- ...alpha1.timeseries-formats.v42.v2beta1.json | 7 +- ...timeseries-gradient-area.v42.v2alpha1.json | 7 +- ....timeseries-gradient-area.v42.v2beta1.json | 7 +- ...timeseries-hue-gradients.v42.v2alpha1.json | 7 +- ....timeseries-hue-gradients.v42.v2beta1.json | 7 +- ...0alpha1.timeseries-nulls.v42.v2alpha1.json | 7 +- ...v0alpha1.timeseries-nulls.v42.v2beta1.json | 7 +- ...1.timeseries-out-of-rage.v42.v2alpha1.json | 7 +- ...a1.timeseries-out-of-rage.v42.v2beta1.json | 7 +- ...-tooltip-cursor-position.v42.v2alpha1.json | 7 +- ...d-tooltip-cursor-position.v42.v2beta1.json | 7 +- ...1.timeseries-soft-limits.v42.v2alpha1.json | 7 +- ...a1.timeseries-soft-limits.v42.v2beta1.json | 7 +- ...pha1.timeseries-stacking.v42.v2alpha1.json | 7 +- ...lpha1.timeseries-stacking.v42.v2beta1.json | 7 +- ...ha1.timeseries-stacking2.v42.v2alpha1.json | 7 +- ...pha1.timeseries-stacking2.v42.v2beta1.json | 7 +- ...a1.timeseries-thresholds.v42.v2alpha1.json | 7 +- ...ha1.timeseries-thresholds.v42.v2beta1.json | 7 +- ...v0alpha1.timeseries-time.v42.v2alpha1.json | 7 +- .../v0alpha1.timeseries-time.v42.v2beta1.json | 7 +- ...es-y-ticks-zero-decimals.v42.v2alpha1.json | 7 +- ...ies-y-ticks-zero-decimals.v42.v2beta1.json | 7 +- ...1.timeseries-yaxis-ticks.v42.v2alpha1.json | 7 +- ...a1.timeseries-yaxis-ticks.v42.v2beta1.json | 7 +- .../v0alpha1.timeseries.v42.v2alpha1.json | 7 +- .../v0alpha1.timeseries.v42.v2beta1.json | 7 +- .../v0alpha1.trend_example.v42.v2alpha1.json | 7 +- .../v0alpha1.trend_example.v42.v2beta1.json | 7 +- .../v0alpha1.xychart-demo.v42.v2alpha1.json | 7 +- .../v0alpha1.xychart-demo.v42.v2beta1.json | 7 +- ...lpha1.xychart-migrations.v42.v2alpha1.json | 7 +- ...alpha1.xychart-migrations.v42.v2beta1.json | 7 +- ...chart-tooltip-color-test.v42.v2alpha1.json | 7 +- ...ychart-tooltip-color-test.v42.v2beta1.json | 7 +- ...1.mostly-blank-dashboard.v42.v2alpha1.json | 7 +- ...a1.mostly-blank-dashboard.v42.v2beta1.json | 7 +- ...lative_time_zone_support.v42.v2alpha1.json | 7 +- ...elative_time_zone_support.v42.v2beta1.json | 7 +- ..._queries_and_annotations.v42.v2alpha1.json | 7 +- ...w_queries_and_annotations.v42.v2beta1.json | 7 +- .../v0alpha1.tall_dashboard.v42.v2alpha1.json | 7 +- .../v0alpha1.tall_dashboard.v42.v2beta1.json | 7 +- ...alpha1.time_zone_support.v42.v2alpha1.json | 7 +- ...0alpha1.time_zone_support.v42.v2beta1.json | 7 +- ...alpha1.config-from-query.v42.v2alpha1.json | 7 +- ...0alpha1.config-from-query.v42.v2beta1.json | 7 +- ...lpha1.extract-json-paths.v42.v2alpha1.json | 7 +- ...alpha1.extract-json-paths.v42.v2beta1.json | 7 +- .../v0alpha1.filter.v42.v2alpha1.json | 7 +- .../v0alpha1.filter.v42.v2beta1.json | 7 +- .../v0alpha1.join-by-field.v42.v2alpha1.json | 7 +- .../v0alpha1.join-by-field.v42.v2beta1.json | 7 +- .../v0alpha1.join-by-labels.v42.v2alpha1.json | 7 +- .../v0alpha1.join-by-labels.v42.v2beta1.json | 7 +- ...pha1.regression-analysis.v42.v2alpha1.json | 7 +- ...lpha1.regression-analysis.v42.v2beta1.json | 7 +- .../v0alpha1.reuse.v42.v2alpha1.json | 7 +- .../v0alpha1.reuse.v42.v2beta1.json | 7 +- .../v0alpha1.rows-to-fields.v42.v2alpha1.json | 7 +- .../v0alpha1.rows-to-fields.v42.v2beta1.json | 7 +- ...beta1.v1beta1.all-panels.v42.v2alpha1.json | 7 +- ...1beta1.v1beta1.all-panels.v42.v2beta1.json | 7 +- ...ha1.v1beta1.v1beta1.home.v42.v2alpha1.json | 7 +- ...pha1.v1beta1.v1beta1.home.v42.v2beta1.json | 7 +- ...beta1.annotation-conversions.v2alpha1.json | 7 +- ...1beta1.annotation-conversions.v2beta1.json | 7 +- ...v1beta1.dashboard-properties.v2alpha1.json | 7 +- .../v1beta1.dashboard-properties.v2beta1.json | 7 +- ...library-panel-repeat-options.v2alpha1.json | 7 +- ....library-panel-repeat-options.v2beta1.json | 7 +- .../v1beta1.panel-conversions.v2alpha1.json | 7 +- .../v1beta1.panel-conversions.v2beta1.json | 7 +- ...l-datasource-type-datasource.v2alpha1.json | 7 +- ...el-datasource-type-datasource.v2beta1.json | 7 +- ...v1beta1.variable-conversions.v2alpha1.json | 7 +- .../v1beta1.variable-conversions.v2beta1.json | 7 +- apps/dashboard/pkg/migration/conversion/v0.go | 76 +- .../pkg/migration/conversion/v0_test.go | 205 +- apps/dashboard/pkg/migration/conversion/v1.go | 46 +- .../pkg/migration/conversion/v1_test.go | 172 +- apps/dashboard/pkg/migration/conversion/v2.go | 21 +- .../pkg/migration/conversion/v2_test.go | 138 + .../conversion/v2alpha1_to_v1beta1.go | 3 +- 417 files changed, 5200 insertions(+), 975 deletions(-) diff --git a/apps/dashboard/pkg/migration/conversion/conversion_data_loss_detection.go b/apps/dashboard/pkg/migration/conversion/conversion_data_loss_detection.go index ec7983b703b..db3353b66a1 100644 --- a/apps/dashboard/pkg/migration/conversion/conversion_data_loss_detection.go +++ b/apps/dashboard/pkg/migration/conversion/conversion_data_loss_detection.go @@ -4,8 +4,6 @@ import ( "errors" "fmt" - "k8s.io/apimachinery/pkg/conversion" - 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" @@ -121,6 +119,14 @@ func countPanelsV0V1(spec map[string]interface{}) int { return count } +// countTargetsFromPanel counts the number of targets/queries in a panel. +func countTargetsFromPanel(panelMap map[string]interface{}) int { + if targets, ok := panelMap["targets"].([]interface{}); ok { + return len(targets) + } + return 0 +} + // countQueriesV0V1 counts data queries in v0alpha1 or v1beta1 dashboard spec // Note: Row panels are layout containers and should not have queries. // We ignore any queries on row panels themselves, but count queries in their collapsed panels. @@ -145,9 +151,7 @@ func countQueriesV0V1(spec map[string]interface{}) int { // Count queries in regular panels (NOT row panels) if panelType != "row" { - if targets, ok := panelMap["targets"].([]interface{}); ok { - count += len(targets) - } + count += countTargetsFromPanel(panelMap) } // Count queries in collapsed panels inside row panels @@ -155,9 +159,7 @@ func countQueriesV0V1(spec map[string]interface{}) int { if collapsedPanels, ok := panelMap["panels"].([]interface{}); ok { for _, cp := range collapsedPanels { if cpMap, ok := cp.(map[string]interface{}); ok { - if targets, ok := cpMap["targets"].([]interface{}); ok { - count += len(targets) - } + count += countTargetsFromPanel(cpMap) } } } @@ -442,77 +444,3 @@ func collectDashboardStats(dashboard interface{}) dashboardStats { } return dashboardStats{} } - -// withConversionDataLossDetection wraps a conversion function to detect data loss -func withConversionDataLossDetection(sourceFuncName, targetFuncName string, conversionFunc func(a, b interface{}, scope conversion.Scope) error) func(a, b interface{}, scope conversion.Scope) error { - return func(a, b interface{}, scope conversion.Scope) error { - // Collect source statistics - var sourceStats dashboardStats - switch source := a.(type) { - case *dashv0.Dashboard: - if source.Spec.Object != nil { - sourceStats = collectStatsV0V1(source.Spec.Object) - } - case *dashv1.Dashboard: - if source.Spec.Object != nil { - sourceStats = collectStatsV0V1(source.Spec.Object) - } - case *dashv2alpha1.Dashboard: - sourceStats = collectStatsV2alpha1(source.Spec) - case *dashv2beta1.Dashboard: - sourceStats = collectStatsV2beta1(source.Spec) - } - - // Execute the conversion - err := conversionFunc(a, b, scope) - if err != nil { - return err - } - - // Collect target statistics - var targetStats dashboardStats - switch target := b.(type) { - case *dashv0.Dashboard: - if target.Spec.Object != nil { - targetStats = collectStatsV0V1(target.Spec.Object) - } - case *dashv1.Dashboard: - if target.Spec.Object != nil { - targetStats = collectStatsV0V1(target.Spec.Object) - } - case *dashv2alpha1.Dashboard: - targetStats = collectStatsV2alpha1(target.Spec) - case *dashv2beta1.Dashboard: - targetStats = collectStatsV2beta1(target.Spec) - } - - // Detect if data was lost - if dataLossErr := detectConversionDataLoss(sourceStats, targetStats, sourceFuncName, targetFuncName); dataLossErr != nil { - getLogger().Error("Dashboard conversion data loss detected", - "sourceFunc", sourceFuncName, - "targetFunc", targetFuncName, - "sourcePanels", sourceStats.panelCount, - "targetPanels", targetStats.panelCount, - "sourceQueries", sourceStats.queryCount, - "targetQueries", targetStats.queryCount, - "sourceAnnotations", sourceStats.annotationCount, - "targetAnnotations", targetStats.annotationCount, - "sourceLinks", sourceStats.linkCount, - "targetLinks", targetStats.linkCount, - "error", dataLossErr, - ) - return dataLossErr - } - - getLogger().Debug("Dashboard conversion completed without data loss", - "sourceFunc", sourceFuncName, - "targetFunc", targetFuncName, - "panels", targetStats.panelCount, - "queries", targetStats.queryCount, - "annotations", targetStats.annotationCount, - "links", targetStats.linkCount, - ) - - return nil - } -} diff --git a/apps/dashboard/pkg/migration/conversion/conversion_data_loss_detection_test.go b/apps/dashboard/pkg/migration/conversion/conversion_data_loss_detection_test.go index 816fd871029..5c28c98b861 100644 --- a/apps/dashboard/pkg/migration/conversion/conversion_data_loss_detection_test.go +++ b/apps/dashboard/pkg/migration/conversion/conversion_data_loss_detection_test.go @@ -410,158 +410,167 @@ func TestCountPanelsV2(t *testing.T) { } func TestDetectConversionDataLoss(t *testing.T) { - tests := []struct { - name string - sourceStats dashboardStats - targetStats dashboardStats - expectError bool - errorMsg string - }{ - { - name: "perfect match - no data loss", - sourceStats: dashboardStats{ - panelCount: 3, - queryCount: 5, - annotationCount: 2, - linkCount: 1, - variableCount: 2, + // Tests that expect NO error (no data loss) + t.Run("success cases", func(t *testing.T) { + successTests := []struct { + name string + sourceStats dashboardStats + targetStats dashboardStats + }{ + { + name: "perfect match - no data loss", + sourceStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + variableCount: 2, + }, + targetStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + variableCount: 2, + }, }, - targetStats: dashboardStats{ - panelCount: 3, - queryCount: 5, - annotationCount: 2, - linkCount: 1, - variableCount: 2, + { + name: "panel count increased (allowed)", + sourceStats: dashboardStats{ + panelCount: 2, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + }, + targetStats: dashboardStats{ + panelCount: 3, // Added a panel (OK) + queryCount: 5, + annotationCount: 2, + linkCount: 1, + }, }, - expectError: false, - }, - { - name: "panel count decreased (data loss)", - sourceStats: dashboardStats{ - panelCount: 3, - queryCount: 5, - annotationCount: 2, - linkCount: 1, + { + name: "annotation count increased (allowed - default annotations)", + sourceStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 0, + linkCount: 1, + }, + targetStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 1, // Added default annotation (OK) + linkCount: 1, + }, }, - targetStats: dashboardStats{ - panelCount: 2, // Lost a panel! - queryCount: 5, - annotationCount: 2, - linkCount: 1, - }, - expectError: true, - errorMsg: "panel count decreased", - }, - { - name: "panel count increased (allowed)", - sourceStats: dashboardStats{ - panelCount: 2, - queryCount: 5, - annotationCount: 2, - linkCount: 1, - }, - targetStats: dashboardStats{ - panelCount: 3, // Added a panel (OK) - queryCount: 5, - annotationCount: 2, - linkCount: 1, - }, - expectError: false, - }, - { - name: "query count decreased (data loss)", - sourceStats: dashboardStats{ - panelCount: 3, - queryCount: 5, - annotationCount: 2, - linkCount: 1, - }, - targetStats: dashboardStats{ - panelCount: 3, - queryCount: 3, // Lost queries! - annotationCount: 2, - linkCount: 1, - }, - expectError: true, - errorMsg: "query count decreased", - }, - { - name: "annotation count decreased (data loss)", - sourceStats: dashboardStats{ - panelCount: 3, - queryCount: 5, - annotationCount: 2, - linkCount: 1, - }, - targetStats: dashboardStats{ - panelCount: 3, - queryCount: 5, - annotationCount: 1, // Lost annotation! - linkCount: 1, - }, - expectError: true, - errorMsg: "annotation count decreased", - }, - { - name: "annotation count increased (allowed - default annotations)", - sourceStats: dashboardStats{ - panelCount: 3, - queryCount: 5, - annotationCount: 0, - linkCount: 1, - }, - targetStats: dashboardStats{ - panelCount: 3, - queryCount: 5, - annotationCount: 1, // Added default annotation (OK) - linkCount: 1, - }, - expectError: false, - }, - { - name: "variable count decreased (data loss)", - sourceStats: dashboardStats{ - panelCount: 3, - queryCount: 5, - annotationCount: 2, - linkCount: 1, - variableCount: 3, - }, - targetStats: dashboardStats{ - panelCount: 3, - queryCount: 5, - annotationCount: 2, - linkCount: 1, - variableCount: 1, // Lost variables! - }, - expectError: true, - errorMsg: "variable count decreased", - }, - { - name: "multiple decreases (data loss)", - sourceStats: dashboardStats{ - panelCount: 3, - queryCount: 5, - annotationCount: 2, - linkCount: 1, - variableCount: 2, - }, - targetStats: dashboardStats{ - panelCount: 2, // Lost panel - queryCount: 3, // Lost queries - annotationCount: 2, - linkCount: 0, // Lost link - variableCount: 2, - }, - expectError: true, - errorMsg: "panel count decreased", - }, - } + } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := detectConversionDataLoss(tt.sourceStats, tt.targetStats, "TestSource", "TestTarget") - if tt.expectError { + for _, tt := range successTests { + t.Run(tt.name, func(t *testing.T) { + err := detectConversionDataLoss(tt.sourceStats, tt.targetStats, "TestSource", "TestTarget") + assert.NoError(t, err) + }) + } + }) + + // Tests that expect an error (data loss detected) + t.Run("data loss cases", func(t *testing.T) { + errorTests := []struct { + name string + sourceStats dashboardStats + targetStats dashboardStats + errorMsg string + }{ + { + name: "panel count decreased (data loss)", + sourceStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + }, + targetStats: dashboardStats{ + panelCount: 2, // Lost a panel! + queryCount: 5, + annotationCount: 2, + linkCount: 1, + }, + errorMsg: "panel count decreased", + }, + { + name: "query count decreased (data loss)", + sourceStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + }, + targetStats: dashboardStats{ + panelCount: 3, + queryCount: 3, // Lost queries! + annotationCount: 2, + linkCount: 1, + }, + errorMsg: "query count decreased", + }, + { + name: "annotation count decreased (data loss)", + sourceStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + }, + targetStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 1, // Lost annotation! + linkCount: 1, + }, + errorMsg: "annotation count decreased", + }, + { + name: "variable count decreased (data loss)", + sourceStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + variableCount: 3, + }, + targetStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + variableCount: 1, // Lost variables! + }, + errorMsg: "variable count decreased", + }, + { + name: "multiple decreases (data loss)", + sourceStats: dashboardStats{ + panelCount: 3, + queryCount: 5, + annotationCount: 2, + linkCount: 1, + variableCount: 2, + }, + targetStats: dashboardStats{ + panelCount: 2, // Lost panel + queryCount: 3, // Lost queries + annotationCount: 2, + linkCount: 0, // Lost link + variableCount: 2, + }, + errorMsg: "panel count decreased", + }, + } + + for _, tt := range errorTests { + t.Run(tt.name, func(t *testing.T) { + err := detectConversionDataLoss(tt.sourceStats, tt.targetStats, "TestSource", "TestTarget") require.Error(t, err) assert.Contains(t, err.Error(), tt.errorMsg) @@ -569,11 +578,9 @@ func TestDetectConversionDataLoss(t *testing.T) { var detectConversionDataLossErr *ConversionDataLossError require.ErrorAs(t, err, &detectConversionDataLossErr) assert.Equal(t, "TestSource_to_TestTarget", detectConversionDataLossErr.GetFunctionName()) - } else { - assert.NoError(t, err) - } - }) - } + }) + } + }) } func TestCollectStatsV0V1(t *testing.T) { @@ -687,140 +694,692 @@ func TestConversionDataLossError(t *testing.T) { assert.Equal(t, "data loss detected in TestFunc (v0alpha1 → v1beta1): test error message", err.Error()) } -// Integration test showing how validation works with actual dashboard types -func TestWithConversionValidation_Integration(t *testing.T) { - // Create a source dashboard (v0) with specific counts - sourceV0 := &dashv0.Dashboard{ - Spec: dashv0.DashboardSpec{ - Object: map[string]interface{}{ - "panels": []interface{}{ - map[string]interface{}{ - "id": float64(1), - "type": "graph", - "targets": []interface{}{ - map[string]interface{}{"refId": "A"}, - }, - }, - map[string]interface{}{ - "id": float64(2), - "type": "table", - "targets": []interface{}{ - map[string]interface{}{"refId": "A"}, - map[string]interface{}{"refId": "B"}, - }, - }, - }, - "annotations": map[string]interface{}{ - "list": []interface{}{ - map[string]interface{}{"name": "Annotation 1"}, - }, - }, - "links": []interface{}{ - map[string]interface{}{"title": "Link 1"}, - }, - }, - }, - } +// TestWithConversionMetrics_DataLossOnlyCheckedOnSuccess verifies that the +// withConversionMetrics wrapper only runs data loss detection when conversion succeeds. +func TestWithConversionMetrics_DataLossOnlyCheckedOnSuccess(t *testing.T) { + conversionError := errors.New("conversion failed") - // Create a target dashboard (v1) with matching counts - targetV1 := &dashv1.Dashboard{ - Spec: dashv1.DashboardSpec{ - Object: map[string]interface{}{ - "panels": []interface{}{ - map[string]interface{}{ - "id": float64(1), - "type": "graph", - "targets": []interface{}{ - map[string]interface{}{"refId": "A"}, - }, - }, - map[string]interface{}{ - "id": float64(2), - "type": "table", - "targets": []interface{}{ - map[string]interface{}{"refId": "A"}, - map[string]interface{}{"refId": "B"}, - }, - }, - }, - "annotations": map[string]interface{}{ - "list": []interface{}{ - map[string]interface{}{"name": "Annotation 1"}, - }, - }, - "links": []interface{}{ - map[string]interface{}{"title": "Link 1"}, - }, - }, - }, - } + // Test 1: Conversion fails - data loss should NOT be checked + t.Run("data loss not checked when conversion fails", func(t *testing.T) { + mockFailingConversion := func(a, b interface{}, scope conversion.Scope) error { + return conversionError + } - // Mock conversion function that just copies the spec - mockConversion := func(a, b interface{}, scope conversion.Scope) error { - source := a.(*dashv0.Dashboard) - target := b.(*dashv1.Dashboard) - target.Spec = dashv1.DashboardSpec{Object: source.Spec.Object} - return nil - } + wrappedFunc := withConversionMetrics(dashv0.APIVERSION, dashv1.APIVERSION, mockFailingConversion) - // Wrap with validation - validatedFunc := withConversionDataLossDetection("V0", "V1beta1", mockConversion) - - // This should pass validation - err := validatedFunc(sourceV0, targetV1, nil) - assert.NoError(t, err) -} - -func TestWithConversionValidation_DataLoss(t *testing.T) { - // Create a source dashboard (v0) with 2 panels - sourceV0 := &dashv0.Dashboard{ - Spec: dashv0.DashboardSpec{ - Object: map[string]interface{}{ - "panels": []interface{}{ - map[string]interface{}{ - "id": float64(1), - "type": "graph", + sourceV0 := &dashv0.Dashboard{ + Spec: dashv0.DashboardSpec{ + Object: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{"id": float64(1), "type": "graph"}, + map[string]interface{}{"id": float64(2), "type": "table"}, }, - map[string]interface{}{ - "id": float64(2), - "type": "table", - }, - }, - }, - }, - } - - targetV1 := &dashv1.Dashboard{} - - // Mock conversion function that loses a panel - mockBadConversion := func(a, b interface{}, scope conversion.Scope) error { - target := b.(*dashv1.Dashboard) - // Only copy 1 panel instead of 2 - target.Spec = dashv1.DashboardSpec{ - Object: map[string]interface{}{ - "panels": []interface{}{ - map[string]interface{}{ - "id": float64(1), - "type": "graph", - }, - // Missing panel 2! }, }, } - return nil + targetV1 := &dashv1.Dashboard{} // Empty - would trigger data loss if checked + + // Execute - withConversionMetrics always returns nil to avoid 500s + err := wrappedFunc(sourceV0, targetV1, nil) + require.NoError(t, err, "withConversionMetrics should return nil even on conversion failure") + }) + + // Test 2: Conversion succeeds but data loss occurs - should detect data loss + t.Run("data loss detected when conversion succeeds but loses data", func(t *testing.T) { + mockDataLosingConversion := func(a, b interface{}, scope conversion.Scope) error { + // Conversion "succeeds" but loses a panel + target := b.(*dashv1.Dashboard) + target.Spec = dashv1.DashboardSpec{ + Object: map[string]interface{}{ + "panels": []interface{}{ + // Only 1 panel instead of 2 + map[string]interface{}{"id": float64(1), "type": "graph"}, + }, + }, + } + return nil + } + + wrappedFunc := withConversionMetrics(dashv0.APIVERSION, dashv1.APIVERSION, mockDataLosingConversion) + + sourceV0 := &dashv0.Dashboard{ + Spec: dashv0.DashboardSpec{ + Object: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{"id": float64(1), "type": "graph"}, + map[string]interface{}{"id": float64(2), "type": "table"}, + }, + }, + }, + } + targetV1 := &dashv1.Dashboard{} + + // Execute - withConversionMetrics always returns nil + err := wrappedFunc(sourceV0, targetV1, nil) + require.NoError(t, err, "withConversionMetrics should return nil even on data loss") + + // The data loss is logged and recorded in metrics, but no error is returned to API server + }) +} + +// TestDataLossDetectionReturnsCorrectError verifies that when data loss is detected, +// a ConversionDataLossError is returned with proper function name and details. +func TestDataLossDetectionReturnsCorrectError(t *testing.T) { + t.Run("panel loss returns ConversionDataLossError", func(t *testing.T) { + sourceV0 := &dashv0.Dashboard{ + Spec: dashv0.DashboardSpec{ + Object: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{"id": float64(1), "type": "graph"}, + map[string]interface{}{"id": float64(2), "type": "table"}, + }, + }, + }, + } + + targetV1 := &dashv1.Dashboard{ + Spec: dashv1.DashboardSpec{ + Object: map[string]interface{}{ + "panels": []interface{}{ + // Missing panel 2 + map[string]interface{}{"id": float64(1), "type": "graph"}, + }, + }, + }, + } + + err := checkConversionDataLoss(dashv0.APIVERSION, dashv1.APIVERSION, sourceV0, targetV1) + + require.Error(t, err) + var dataLossErr *ConversionDataLossError + require.ErrorAs(t, err, &dataLossErr) + assert.Equal(t, "V0_to_V1", dataLossErr.GetFunctionName()) + assert.Contains(t, err.Error(), "panel count decreased") + }) + + t.Run("query loss returns ConversionDataLossError", func(t *testing.T) { + sourceV0 := &dashv0.Dashboard{ + Spec: dashv0.DashboardSpec{ + Object: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + map[string]interface{}{"refId": "B"}, + }, + }, + }, + }, + }, + } + + targetV1 := &dashv1.Dashboard{ + Spec: dashv1.DashboardSpec{ + Object: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + "targets": []interface{}{ + // Missing query B + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + }, + } + + err := checkConversionDataLoss(dashv0.APIVERSION, dashv1.APIVERSION, sourceV0, targetV1) + + require.Error(t, err) + var dataLossErr *ConversionDataLossError + require.ErrorAs(t, err, &dataLossErr) + assert.Contains(t, err.Error(), "query count decreased") + }) + + t.Run("no data loss returns nil", func(t *testing.T) { + sourceV0 := &dashv0.Dashboard{ + Spec: dashv0.DashboardSpec{ + Object: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + }, + } + + targetV1 := &dashv1.Dashboard{ + Spec: dashv1.DashboardSpec{ + Object: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + }, + } + + err := checkConversionDataLoss(dashv0.APIVERSION, dashv1.APIVERSION, sourceV0, targetV1) + + require.NoError(t, err) + }) +} + +// TestDataLossDetection_AllTypesAllVersions verifies that data loss is detected for +// all types of loss (panels, queries, annotations, links, variables) across all +// version permutations (v0alpha1, v1beta1, v2alpha1, v2beta1). +func TestDataLossDetection_AllTypesAllVersions(t *testing.T) { + // Helper to create v0/v1 unstructured spec with data + createV0V1SpecWithData := func() map[string]interface{} { + return map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + map[string]interface{}{"refId": "B"}, + }, + }, + map[string]interface{}{ + "id": float64(2), + "type": "table", + "targets": []interface{}{ + map[string]interface{}{"refId": "C"}, + }, + }, + }, + "annotations": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{"name": "Annotation 1"}, + map[string]interface{}{"name": "Annotation 2"}, + }, + }, + "links": []interface{}{ + map[string]interface{}{"title": "Link 1"}, + map[string]interface{}{"title": "Link 2"}, + }, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{"name": "var1", "type": "query"}, + map[string]interface{}{"name": "var2", "type": "custom"}, + }, + }, + } } - // Wrap with data loss detection - validatedFunc := withConversionDataLossDetection("V0", "V1beta1", mockBadConversion) + // Helper to create v0/v1 unstructured spec with data loss (missing items) + createV0V1SpecWithLoss := func(lossType string) map[string]interface{} { + spec := createV0V1SpecWithData() + switch lossType { + case "panel": + spec["panels"] = []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + map[string]interface{}{"refId": "B"}, + }, + }, + // Missing panel 2 + } + case "query": + spec["panels"] = []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + // Missing query B + }, + }, + map[string]interface{}{ + "id": float64(2), + "type": "table", + "targets": []interface{}{ + map[string]interface{}{"refId": "C"}, + }, + }, + } + case "annotation": + spec["annotations"] = map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{"name": "Annotation 1"}, + // Missing annotation 2 + }, + } + case "link": + spec["links"] = []interface{}{ + map[string]interface{}{"title": "Link 1"}, + // Missing link 2 + } + case "variable": + spec["templating"] = map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{"name": "var1", "type": "query"}, + // Missing var2 + }, + } + } + return spec + } - // This should fail due to data loss - err := validatedFunc(sourceV0, targetV1, nil) - require.Error(t, err) - assert.Contains(t, err.Error(), "panel count decreased") + // Helper to create v2alpha1 dashboard with data + createV2alpha1WithData := func() *dashv2alpha1.Dashboard { + return &dashv2alpha1.Dashboard{ + Spec: dashv2alpha1.DashboardSpec{ + Elements: map[string]dashv2alpha1.DashboardElement{ + "panel1": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 1, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{ + {Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "A"}}, + {Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "B"}}, + }, + }, + }, + }, + }, + }, + "panel2": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 2, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{ + {Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "C"}}, + }, + }, + }, + }, + }, + }, + }, + Annotations: []dashv2alpha1.DashboardAnnotationQueryKind{{}, {}}, + Links: []dashv2alpha1.DashboardDashboardLink{{}, {}}, + Variables: []dashv2alpha1.DashboardVariableKind{{}, {}}, + }, + } + } - var detectConversionDataLossErr *ConversionDataLossError - require.ErrorAs(t, err, &detectConversionDataLossErr) - assert.Equal(t, "V0_to_V1beta1", detectConversionDataLossErr.GetFunctionName()) + // Helper to create v2alpha1 dashboard with data loss + createV2alpha1WithLoss := func(lossType string) *dashv2alpha1.Dashboard { + dash := createV2alpha1WithData() + switch lossType { + case "panel": + delete(dash.Spec.Elements, "panel2") + case "query": + dash.Spec.Elements["panel1"].PanelKind.Spec.Data.Spec.Queries = []dashv2alpha1.DashboardPanelQueryKind{ + {Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "A"}}, + // Missing query B + } + case "annotation": + dash.Spec.Annotations = []dashv2alpha1.DashboardAnnotationQueryKind{{}} + case "link": + dash.Spec.Links = []dashv2alpha1.DashboardDashboardLink{{}} + case "variable": + dash.Spec.Variables = []dashv2alpha1.DashboardVariableKind{{}} + } + return dash + } + + // Helper to create v2beta1 dashboard with data + createV2beta1WithData := func() *dashv2beta1.Dashboard { + return &dashv2beta1.Dashboard{ + Spec: dashv2beta1.DashboardSpec{ + Elements: map[string]dashv2beta1.DashboardElement{ + "panel1": { + PanelKind: &dashv2beta1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2beta1.DashboardPanelSpec{ + Id: 1, + Data: dashv2beta1.DashboardQueryGroupKind{ + Spec: dashv2beta1.DashboardQueryGroupSpec{ + Queries: []dashv2beta1.DashboardPanelQueryKind{ + {Spec: dashv2beta1.DashboardPanelQuerySpec{RefId: "A"}}, + {Spec: dashv2beta1.DashboardPanelQuerySpec{RefId: "B"}}, + }, + }, + }, + }, + }, + }, + "panel2": { + PanelKind: &dashv2beta1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2beta1.DashboardPanelSpec{ + Id: 2, + Data: dashv2beta1.DashboardQueryGroupKind{ + Spec: dashv2beta1.DashboardQueryGroupSpec{ + Queries: []dashv2beta1.DashboardPanelQueryKind{ + {Spec: dashv2beta1.DashboardPanelQuerySpec{RefId: "C"}}, + }, + }, + }, + }, + }, + }, + }, + Annotations: []dashv2beta1.DashboardAnnotationQueryKind{{}, {}}, + Links: []dashv2beta1.DashboardDashboardLink{{}, {}}, + Variables: []dashv2beta1.DashboardVariableKind{{}, {}}, + }, + } + } + + // Helper to create v2beta1 dashboard with data loss + createV2beta1WithLoss := func(lossType string) *dashv2beta1.Dashboard { + dash := createV2beta1WithData() + switch lossType { + case "panel": + delete(dash.Spec.Elements, "panel2") + case "query": + dash.Spec.Elements["panel1"].PanelKind.Spec.Data.Spec.Queries = []dashv2beta1.DashboardPanelQueryKind{ + {Spec: dashv2beta1.DashboardPanelQuerySpec{RefId: "A"}}, + // Missing query B + } + case "annotation": + dash.Spec.Annotations = []dashv2beta1.DashboardAnnotationQueryKind{{}} + case "link": + dash.Spec.Links = []dashv2beta1.DashboardDashboardLink{{}} + case "variable": + dash.Spec.Variables = []dashv2beta1.DashboardVariableKind{{}} + } + return dash + } + + // All loss types to test + lossTypes := []struct { + name string + expectedErrMsg string + }{ + {"panel", "panel count decreased"}, + {"query", "query count decreased"}, + {"annotation", "annotation count decreased"}, + {"link", "link count decreased"}, + {"variable", "variable count decreased"}, + } + + // All version permutations (12 total: 4 versions × 3 targets each) + versionPairs := []struct { + sourceVersion string + targetVersion string + sourceAPI string + targetAPI string + }{ + // From v0alpha1 + {"v0alpha1", "v1beta1", dashv0.APIVERSION, dashv1.APIVERSION}, + {"v0alpha1", "v2alpha1", dashv0.APIVERSION, dashv2alpha1.APIVERSION}, + {"v0alpha1", "v2beta1", dashv0.APIVERSION, dashv2beta1.APIVERSION}, + // From v1beta1 + {"v1beta1", "v0alpha1", dashv1.APIVERSION, dashv0.APIVERSION}, + {"v1beta1", "v2alpha1", dashv1.APIVERSION, dashv2alpha1.APIVERSION}, + {"v1beta1", "v2beta1", dashv1.APIVERSION, dashv2beta1.APIVERSION}, + // From v2alpha1 + {"v2alpha1", "v0alpha1", dashv2alpha1.APIVERSION, dashv0.APIVERSION}, + {"v2alpha1", "v1beta1", dashv2alpha1.APIVERSION, dashv1.APIVERSION}, + {"v2alpha1", "v2beta1", dashv2alpha1.APIVERSION, dashv2beta1.APIVERSION}, + // From v2beta1 + {"v2beta1", "v0alpha1", dashv2beta1.APIVERSION, dashv0.APIVERSION}, + {"v2beta1", "v1beta1", dashv2beta1.APIVERSION, dashv1.APIVERSION}, + {"v2beta1", "v2alpha1", dashv2beta1.APIVERSION, dashv2alpha1.APIVERSION}, + } + + for _, vp := range versionPairs { + for _, lt := range lossTypes { + testName := fmt.Sprintf("%s_to_%s_%s_loss", vp.sourceVersion, vp.targetVersion, lt.name) + t.Run(testName, func(t *testing.T) { + var source, target interface{} + + // Create source dashboard with full data + switch vp.sourceVersion { + case "v0alpha1": + source = &dashv0.Dashboard{Spec: dashv0.DashboardSpec{Object: createV0V1SpecWithData()}} + case "v1beta1": + source = &dashv1.Dashboard{Spec: dashv1.DashboardSpec{Object: createV0V1SpecWithData()}} + case "v2alpha1": + source = createV2alpha1WithData() + case "v2beta1": + source = createV2beta1WithData() + } + + // Create target dashboard with data loss + switch vp.targetVersion { + case "v0alpha1": + target = &dashv0.Dashboard{Spec: dashv0.DashboardSpec{Object: createV0V1SpecWithLoss(lt.name)}} + case "v1beta1": + target = &dashv1.Dashboard{Spec: dashv1.DashboardSpec{Object: createV0V1SpecWithLoss(lt.name)}} + case "v2alpha1": + target = createV2alpha1WithLoss(lt.name) + case "v2beta1": + target = createV2beta1WithLoss(lt.name) + } + + // Check for data loss + err := checkConversionDataLoss(vp.sourceAPI, vp.targetAPI, source, target) + + // Verify data loss was detected + require.Error(t, err, "Expected %s loss to be detected for %s -> %s", lt.name, vp.sourceVersion, vp.targetVersion) + assert.Contains(t, err.Error(), lt.expectedErrMsg) + + // Verify it's a ConversionDataLossError + var dataLossErr *ConversionDataLossError + require.ErrorAs(t, err, &dataLossErr) + }) + } + } +} + +// TestDataLossDetection_NoFalsePositives_AllVersionPermutations verifies that no false positives +// are triggered for any attribute type when counts are equal or increased, across ALL version permutations. +func TestDataLossDetection_NoFalsePositives_AllVersionPermutations(t *testing.T) { + // Helper to create v0/v1 spec with data + createV0V1SpecWithData := func() map[string]interface{} { + return map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(1), + "type": "graph", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + map[string]interface{}{"refId": "B"}, + }, + }, + map[string]interface{}{ + "id": float64(2), + "type": "table", + "targets": []interface{}{ + map[string]interface{}{"refId": "C"}, + }, + }, + }, + "annotations": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{"name": "Annotation 1"}, + map[string]interface{}{"name": "Annotation 2"}, + }, + }, + "links": []interface{}{ + map[string]interface{}{"title": "Link 1"}, + map[string]interface{}{"title": "Link 2"}, + }, + "templating": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{"name": "var1", "type": "query"}, + map[string]interface{}{"name": "var2", "type": "custom"}, + }, + }, + } + } + + // Helper to create v2alpha1 dashboard with data + createV2alpha1WithData := func() *dashv2alpha1.Dashboard { + return &dashv2alpha1.Dashboard{ + Spec: dashv2alpha1.DashboardSpec{ + Elements: map[string]dashv2alpha1.DashboardElement{ + "panel1": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 1, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{ + {Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "A"}}, + {Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "B"}}, + }, + }, + }, + }, + }, + }, + "panel2": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 2, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{ + {Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "C"}}, + }, + }, + }, + }, + }, + }, + }, + Annotations: []dashv2alpha1.DashboardAnnotationQueryKind{{}, {}}, + Links: []dashv2alpha1.DashboardDashboardLink{{}, {}}, + Variables: []dashv2alpha1.DashboardVariableKind{{}, {}}, + }, + } + } + + // Helper to create v2beta1 dashboard with data + createV2beta1WithData := func() *dashv2beta1.Dashboard { + return &dashv2beta1.Dashboard{ + Spec: dashv2beta1.DashboardSpec{ + Elements: map[string]dashv2beta1.DashboardElement{ + "panel1": { + PanelKind: &dashv2beta1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2beta1.DashboardPanelSpec{ + Id: 1, + Data: dashv2beta1.DashboardQueryGroupKind{ + Spec: dashv2beta1.DashboardQueryGroupSpec{ + Queries: []dashv2beta1.DashboardPanelQueryKind{ + {Spec: dashv2beta1.DashboardPanelQuerySpec{RefId: "A"}}, + {Spec: dashv2beta1.DashboardPanelQuerySpec{RefId: "B"}}, + }, + }, + }, + }, + }, + }, + "panel2": { + PanelKind: &dashv2beta1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2beta1.DashboardPanelSpec{ + Id: 2, + Data: dashv2beta1.DashboardQueryGroupKind{ + Spec: dashv2beta1.DashboardQueryGroupSpec{ + Queries: []dashv2beta1.DashboardPanelQueryKind{ + {Spec: dashv2beta1.DashboardPanelQuerySpec{RefId: "C"}}, + }, + }, + }, + }, + }, + }, + }, + Annotations: []dashv2beta1.DashboardAnnotationQueryKind{{}, {}}, + Links: []dashv2beta1.DashboardDashboardLink{{}, {}}, + Variables: []dashv2beta1.DashboardVariableKind{{}, {}}, + }, + } + } + + // All 12 version permutations + versionPairs := []struct { + sourceVersion string + targetVersion string + sourceAPI string + targetAPI string + }{ + // From v0alpha1 + {"v0alpha1", "v1beta1", dashv0.APIVERSION, dashv1.APIVERSION}, + {"v0alpha1", "v2alpha1", dashv0.APIVERSION, dashv2alpha1.APIVERSION}, + {"v0alpha1", "v2beta1", dashv0.APIVERSION, dashv2beta1.APIVERSION}, + // From v1beta1 + {"v1beta1", "v0alpha1", dashv1.APIVERSION, dashv0.APIVERSION}, + {"v1beta1", "v2alpha1", dashv1.APIVERSION, dashv2alpha1.APIVERSION}, + {"v1beta1", "v2beta1", dashv1.APIVERSION, dashv2beta1.APIVERSION}, + // From v2alpha1 + {"v2alpha1", "v0alpha1", dashv2alpha1.APIVERSION, dashv0.APIVERSION}, + {"v2alpha1", "v1beta1", dashv2alpha1.APIVERSION, dashv1.APIVERSION}, + {"v2alpha1", "v2beta1", dashv2alpha1.APIVERSION, dashv2beta1.APIVERSION}, + // From v2beta1 + {"v2beta1", "v0alpha1", dashv2beta1.APIVERSION, dashv0.APIVERSION}, + {"v2beta1", "v1beta1", dashv2beta1.APIVERSION, dashv1.APIVERSION}, + {"v2beta1", "v2alpha1", dashv2beta1.APIVERSION, dashv2alpha1.APIVERSION}, + } + + for _, vp := range versionPairs { + testName := fmt.Sprintf("%s_to_%s_no_loss", vp.sourceVersion, vp.targetVersion) + t.Run(testName, func(t *testing.T) { + var source, target interface{} + + // Create source dashboard with full data + switch vp.sourceVersion { + case "v0alpha1": + source = &dashv0.Dashboard{Spec: dashv0.DashboardSpec{Object: createV0V1SpecWithData()}} + case "v1beta1": + source = &dashv1.Dashboard{Spec: dashv1.DashboardSpec{Object: createV0V1SpecWithData()}} + case "v2alpha1": + source = createV2alpha1WithData() + case "v2beta1": + source = createV2beta1WithData() + } + + // Create target dashboard with same data (no loss) + switch vp.targetVersion { + case "v0alpha1": + target = &dashv0.Dashboard{Spec: dashv0.DashboardSpec{Object: createV0V1SpecWithData()}} + case "v1beta1": + target = &dashv1.Dashboard{Spec: dashv1.DashboardSpec{Object: createV0V1SpecWithData()}} + case "v2alpha1": + target = createV2alpha1WithData() + case "v2beta1": + target = createV2beta1WithData() + } + + // Check for data loss - should be none + err := checkConversionDataLoss(vp.sourceAPI, vp.targetAPI, source, target) + require.NoError(t, err, "Expected no data loss when data is preserved for %s -> %s", vp.sourceVersion, vp.targetVersion) + }) + } } // TestDataLossDetectionOnAllInputFiles tests all conversions from testdata/input @@ -1359,3 +1918,1148 @@ func extractQueryInfoV2beta1(spec dashv2beta1.DashboardSpec) []queryInfo { return queries } + +// TestDataLossDetection_IntegrationWithRealConversions performs actual conversions +// using the scheme and verifies that data loss detection works correctly. +func TestDataLossDetection_IntegrationWithRealConversions(t *testing.T) { + // Initialize the migrator with a test data source provider + dsProvider := testutil.NewDataSourceProvider(testutil.StandardTestConfig) + leProvider := testutil.NewLibraryElementProvider() + migration.Initialize(dsProvider, leProvider, migration.DefaultCacheTTL) + + // Set up conversion scheme + scheme := runtime.NewScheme() + err := RegisterConversions(scheme, dsProvider, leProvider) + require.NoError(t, err) + + // + // ========== V0/V1 -> V2 CONVERSIONS (These should all pass) ========== + // + + // Test 1: V0 Collapsed Rows -> V2alpha1 + t.Run("Integration_V0CollapsedRows_To_V2alpha1", func(t *testing.T) { + source := &dashv0.Dashboard{ + TypeMeta: metav1.TypeMeta{ + APIVersion: dashv0.APIVERSION, + Kind: "Dashboard", + }, + Spec: dashv0.DashboardSpec{ + Object: createV0V1WithCollapsedRows(2, 2, 1), + }, + } + sourceStats := collectStatsV0V1(source.Spec.Object) + + target := &dashv2alpha1.Dashboard{} + err := scheme.Convert(source, target, nil) + require.NoError(t, err, "Conversion should succeed") + + targetStats := collectStatsV2alpha1(target.Spec) + + t.Logf("V0 Collapsed Rows -> V2alpha1:") + t.Logf(" Source: panels=%d, queries=%d", sourceStats.panelCount, sourceStats.queryCount) + t.Logf(" Target: panels=%d, queries=%d", targetStats.panelCount, targetStats.queryCount) + + assert.Equal(t, sourceStats.panelCount, targetStats.panelCount, "Panel count should match") + assert.Equal(t, sourceStats.queryCount, targetStats.queryCount, "Query count should match") + }) + + // Test 2: V0 Expanded Rows -> V2alpha1 + t.Run("Integration_V0ExpandedRows_To_V2alpha1", func(t *testing.T) { + source := &dashv0.Dashboard{ + TypeMeta: metav1.TypeMeta{ + APIVersion: dashv0.APIVERSION, + Kind: "Dashboard", + }, + Spec: dashv0.DashboardSpec{ + Object: createV0V1WithExpandedRows(2, 2, 1), + }, + } + sourceStats := collectStatsV0V1(source.Spec.Object) + + target := &dashv2alpha1.Dashboard{} + err := scheme.Convert(source, target, nil) + require.NoError(t, err, "Conversion should succeed") + + targetStats := collectStatsV2alpha1(target.Spec) + + t.Logf("V0 Expanded Rows -> V2alpha1:") + t.Logf(" Source: panels=%d, queries=%d", sourceStats.panelCount, sourceStats.queryCount) + t.Logf(" Target: panels=%d, queries=%d", targetStats.panelCount, targetStats.queryCount) + + assert.Equal(t, sourceStats.panelCount, targetStats.panelCount, "Panel count should match") + assert.Equal(t, sourceStats.queryCount, targetStats.queryCount, "Query count should match") + }) + + // Test 3: V0 Flat Panels -> V2alpha1 + t.Run("Integration_V0FlatPanels_To_V2alpha1", func(t *testing.T) { + source := &dashv0.Dashboard{ + TypeMeta: metav1.TypeMeta{ + APIVersion: dashv0.APIVERSION, + Kind: "Dashboard", + }, + Spec: dashv0.DashboardSpec{ + Object: createV0V1FlatPanels(4, 1), + }, + } + sourceStats := collectStatsV0V1(source.Spec.Object) + + target := &dashv2alpha1.Dashboard{} + err := scheme.Convert(source, target, nil) + require.NoError(t, err, "Conversion should succeed") + + targetStats := collectStatsV2alpha1(target.Spec) + + t.Logf("V0 Flat Panels -> V2alpha1:") + t.Logf(" Source: panels=%d, queries=%d", sourceStats.panelCount, sourceStats.queryCount) + t.Logf(" Target: panels=%d, queries=%d", targetStats.panelCount, targetStats.queryCount) + + assert.Equal(t, sourceStats.panelCount, targetStats.panelCount, "Panel count should match") + assert.Equal(t, sourceStats.queryCount, targetStats.queryCount, "Query count should match") + }) + + // Test 4: V0 Mixed Layout -> V2alpha1 + t.Run("Integration_V0MixedLayout_To_V2alpha1", func(t *testing.T) { + source := &dashv0.Dashboard{ + TypeMeta: metav1.TypeMeta{ + APIVersion: dashv0.APIVERSION, + Kind: "Dashboard", + }, + Spec: dashv0.DashboardSpec{ + Object: createV0V1MixedAllTypes(), + }, + } + sourceStats := collectStatsV0V1(source.Spec.Object) + + target := &dashv2alpha1.Dashboard{} + err := scheme.Convert(source, target, nil) + require.NoError(t, err, "Conversion should succeed") + + targetStats := collectStatsV2alpha1(target.Spec) + + t.Logf("V0 Mixed Layout -> V2alpha1:") + t.Logf(" Source: panels=%d, queries=%d", sourceStats.panelCount, sourceStats.queryCount) + t.Logf(" Target: panels=%d, queries=%d", targetStats.panelCount, targetStats.queryCount) + + assert.Equal(t, sourceStats.panelCount, targetStats.panelCount, "Panel count should match") + assert.Equal(t, sourceStats.queryCount, targetStats.queryCount, "Query count should match") + }) + + // + // ========== V2 -> V0/V1 CONVERSIONS ========== + // These tests document the current state of V2 -> V0 conversions. + // + + // Test 5: V2 Simple Tabs -> V0alpha1 + t.Run("Integration_V2SimpleTabs_To_V0alpha1", func(t *testing.T) { + source := createV2DashboardWithSimpleTabs() + source.TypeMeta = metav1.TypeMeta{ + APIVersion: dashv2alpha1.APIVERSION, + Kind: "Dashboard", + } + sourceStats := collectStatsV2alpha1(source.Spec) + + target := &dashv0.Dashboard{} + err := scheme.Convert(source, target, nil) + require.NoError(t, err, "Conversion should succeed") + + targetStats := collectStatsV0V1(target.Spec.Object) + + t.Logf("V2 Simple Tabs -> V0alpha1:") + t.Logf(" Source: panels=%d, queries=%d", sourceStats.panelCount, sourceStats.queryCount) + t.Logf(" Target: panels=%d, queries=%d", targetStats.panelCount, targetStats.queryCount) + + assert.Equal(t, sourceStats.panelCount, targetStats.panelCount, "Panel count should match") + assert.Equal(t, sourceStats.queryCount, targetStats.queryCount, "Query count should match") + }) + + // Test 6: V2 Rows Collapsed -> V0alpha1 + t.Run("Integration_V2RowsCollapsed_To_V0alpha1", func(t *testing.T) { + source := createV2DashboardWithRowsCollapsed() + source.TypeMeta = metav1.TypeMeta{ + APIVersion: dashv2alpha1.APIVERSION, + Kind: "Dashboard", + } + sourceStats := collectStatsV2alpha1(source.Spec) + + target := &dashv0.Dashboard{} + err := scheme.Convert(source, target, nil) + require.NoError(t, err, "Conversion should succeed") + + targetStats := collectStatsV0V1(target.Spec.Object) + + t.Logf("V2 Rows Collapsed -> V0alpha1:") + t.Logf(" Source: panels=%d, queries=%d", sourceStats.panelCount, sourceStats.queryCount) + t.Logf(" Target: panels=%d, queries=%d", targetStats.panelCount, targetStats.queryCount) + + assert.Equal(t, sourceStats.panelCount, targetStats.panelCount, "Panel count should match") + assert.Equal(t, sourceStats.queryCount, targetStats.queryCount, "Query count should match") + }) + + // Test 7: V2 Rows Expanded -> V0alpha1 + t.Run("Integration_V2RowsExpanded_To_V0alpha1", func(t *testing.T) { + source := createV2DashboardWithRowsExpanded() + source.TypeMeta = metav1.TypeMeta{ + APIVersion: dashv2alpha1.APIVERSION, + Kind: "Dashboard", + } + sourceStats := collectStatsV2alpha1(source.Spec) + + target := &dashv0.Dashboard{} + err := scheme.Convert(source, target, nil) + require.NoError(t, err, "Conversion should succeed") + + targetStats := collectStatsV0V1(target.Spec.Object) + + t.Logf("V2 Rows Expanded -> V0alpha1:") + t.Logf(" Source: panels=%d, queries=%d", sourceStats.panelCount, sourceStats.queryCount) + t.Logf(" Target: panels=%d, queries=%d", targetStats.panelCount, targetStats.queryCount) + + assert.Equal(t, sourceStats.panelCount, targetStats.panelCount, "Panel count should match") + assert.Equal(t, sourceStats.queryCount, targetStats.queryCount, "Query count should match") + }) + + // Test 8: V2 Tabs with Nested Rows -> V0alpha1 + t.Run("Integration_V2TabsWithNestedRows_To_V0alpha1", func(t *testing.T) { + source := createV2DashboardWithTabsAndNestedRows() + source.TypeMeta = metav1.TypeMeta{ + APIVersion: dashv2alpha1.APIVERSION, + Kind: "Dashboard", + } + sourceStats := collectStatsV2alpha1(source.Spec) + + target := &dashv0.Dashboard{} + err := scheme.Convert(source, target, nil) + require.NoError(t, err, "Conversion should succeed") + + targetStats := collectStatsV0V1(target.Spec.Object) + + t.Logf("V2 Tabs with Nested Rows -> V0alpha1:") + t.Logf(" Source: panels=%d, queries=%d", sourceStats.panelCount, sourceStats.queryCount) + t.Logf(" Target: panels=%d, queries=%d", targetStats.panelCount, targetStats.queryCount) + + assert.Equal(t, sourceStats.panelCount, targetStats.panelCount, "Panel count should match") + assert.Equal(t, sourceStats.queryCount, targetStats.queryCount, "Query count should match") + }) + + // Test 9: V2 Rows with Nested Tabs -> V0alpha1 + t.Run("Integration_V2RowsWithNestedTabs_To_V0alpha1", func(t *testing.T) { + source := createV2DashboardWithRowsAndNestedTabs() + source.TypeMeta = metav1.TypeMeta{ + APIVersion: dashv2alpha1.APIVERSION, + Kind: "Dashboard", + } + sourceStats := collectStatsV2alpha1(source.Spec) + + target := &dashv0.Dashboard{} + err := scheme.Convert(source, target, nil) + require.NoError(t, err, "Conversion should succeed") + + targetStats := collectStatsV0V1(target.Spec.Object) + + t.Logf("V2 Rows with Nested Tabs -> V0alpha1:") + t.Logf(" Source: panels=%d, queries=%d", sourceStats.panelCount, sourceStats.queryCount) + t.Logf(" Target: panels=%d, queries=%d", targetStats.panelCount, targetStats.queryCount) + + assert.Equal(t, sourceStats.panelCount, targetStats.panelCount, "Panel count should match") + assert.Equal(t, sourceStats.queryCount, targetStats.queryCount, "Query count should match") + }) + + // Test 10: V2 Complex Nesting -> V0alpha1 + t.Run("Integration_V2ComplexNesting_To_V0alpha1", func(t *testing.T) { + source := createV2ComplexNestedDashboard() + source.TypeMeta = metav1.TypeMeta{ + APIVersion: dashv2alpha1.APIVERSION, + Kind: "Dashboard", + } + sourceStats := collectStatsV2alpha1(source.Spec) + + target := &dashv0.Dashboard{} + err := scheme.Convert(source, target, nil) + require.NoError(t, err, "Conversion should succeed") + + targetStats := collectStatsV0V1(target.Spec.Object) + + t.Logf("V2 Complex Nesting -> V0alpha1:") + t.Logf(" Source: panels=%d, queries=%d", sourceStats.panelCount, sourceStats.queryCount) + t.Logf(" Target: panels=%d, queries=%d", targetStats.panelCount, targetStats.queryCount) + + assert.Equal(t, sourceStats.panelCount, targetStats.panelCount, "Panel count should match") + assert.Equal(t, sourceStats.queryCount, targetStats.queryCount, "Query count should match") + }) + + // + // ========== ROUND-TRIP CONVERSIONS ========== + // + + // Test 11: V2 -> V0 -> V2 round-trip (panel count should be preserved) + t.Run("Integration_RoundTrip_V2_V0_V2", func(t *testing.T) { + original := createV2DashboardWithSimpleTabs() + original.TypeMeta = metav1.TypeMeta{ + APIVersion: dashv2alpha1.APIVERSION, + Kind: "Dashboard", + } + originalStats := collectStatsV2alpha1(original.Spec) + + // V2 -> V0 + intermediate := &dashv0.Dashboard{} + err := scheme.Convert(original, intermediate, nil) + require.NoError(t, err, "V2 -> V0 conversion should succeed") + intermediateStats := collectStatsV0V1(intermediate.Spec.Object) + + // V0 -> V2 + final := &dashv2alpha1.Dashboard{} + err = scheme.Convert(intermediate, final, nil) + require.NoError(t, err, "V0 -> V2 conversion should succeed") + finalStats := collectStatsV2alpha1(final.Spec) + + t.Logf("Round-trip V2 -> V0 -> V2:") + t.Logf(" Original (V2): panels=%d, queries=%d", originalStats.panelCount, originalStats.queryCount) + t.Logf(" Intermediate (V0): panels=%d, queries=%d", intermediateStats.panelCount, intermediateStats.queryCount) + t.Logf(" Final (V2): panels=%d, queries=%d", finalStats.panelCount, finalStats.queryCount) + + // Panel count should be preserved through round-trip + assert.Equal(t, originalStats.panelCount, finalStats.panelCount, "Panel count should match after round-trip") + + // Document query behavior + if intermediateStats.queryCount < originalStats.queryCount { + t.Logf(" ⚠️ Queries lost in V2->V0: %d -> %d", originalStats.queryCount, intermediateStats.queryCount) + } + if finalStats.queryCount != originalStats.queryCount { + t.Logf(" ⚠️ Final query count differs: original=%d, final=%d", originalStats.queryCount, finalStats.queryCount) + } + }) + + // Test 12: V0 -> V2 -> V0 round-trip + t.Run("Integration_RoundTrip_V0_V2_V0", func(t *testing.T) { + original := &dashv0.Dashboard{ + TypeMeta: metav1.TypeMeta{ + APIVersion: dashv0.APIVERSION, + Kind: "Dashboard", + }, + Spec: dashv0.DashboardSpec{ + Object: createV0V1MixedAllTypes(), + }, + } + originalStats := collectStatsV0V1(original.Spec.Object) + + // V0 -> V2 + intermediate := &dashv2alpha1.Dashboard{} + err := scheme.Convert(original, intermediate, nil) + require.NoError(t, err, "V0 -> V2 conversion should succeed") + intermediateStats := collectStatsV2alpha1(intermediate.Spec) + + // V2 -> V0 + final := &dashv0.Dashboard{} + err = scheme.Convert(intermediate, final, nil) + require.NoError(t, err, "V2 -> V0 conversion should succeed") + finalStats := collectStatsV0V1(final.Spec.Object) + + t.Logf("Round-trip V0 -> V2 -> V0:") + t.Logf(" Original (V0): panels=%d, queries=%d", originalStats.panelCount, originalStats.queryCount) + t.Logf(" Intermediate (V2): panels=%d, queries=%d", intermediateStats.panelCount, intermediateStats.queryCount) + t.Logf(" Final (V0): panels=%d, queries=%d", finalStats.panelCount, finalStats.queryCount) + + // V0 -> V2 should preserve everything + assert.Equal(t, originalStats.panelCount, intermediateStats.panelCount, "V0->V2 should preserve panels") + assert.Equal(t, originalStats.queryCount, intermediateStats.queryCount, "V0->V2 should preserve queries") + + // Document what happens in V2 -> V0 + assert.Equal(t, originalStats.panelCount, finalStats.panelCount, "Panel count should match after round-trip") + + if finalStats.queryCount < originalStats.queryCount { + t.Logf(" ⚠️ Queries lost in V2->V0 leg: %d -> %d", intermediateStats.queryCount, finalStats.queryCount) + } + }) +} + +// Helper functions to create test dashboards + +// ========== V2 Dashboard Fixtures ========== + +// createV2DashboardWithSimpleTabs creates a V2 dashboard with simple TabsLayout +// Structure: TabsLayout > Tab1[panel1, panel2] + Tab2[panel3, panel4] +func createV2DashboardWithSimpleTabs() *dashv2alpha1.Dashboard { + elements := map[string]dashv2alpha1.DashboardElement{} + for i := 1; i <= 4; i++ { + tabNum := (i-1)/2 + 1 + elements[fmt.Sprintf("panel%d", i)] = dashv2alpha1.DashboardElement{ + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: float64(i), + Title: fmt.Sprintf("Panel %d - Tab %d", i, tabNum), + VizConfig: dashv2alpha1.DashboardVizConfigKind{Kind: "timeseries"}, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{ + {Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "A"}}, + }, + }, + }, + }, + }, + } + } + + return &dashv2alpha1.Dashboard{ + Spec: dashv2alpha1.DashboardSpec{ + Elements: elements, + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + TabsLayoutKind: &dashv2alpha1.DashboardTabsLayoutKind{ + Kind: "TabsLayout", + Spec: dashv2alpha1.DashboardTabsLayoutSpec{ + Tabs: []dashv2alpha1.DashboardTabsLayoutTabKind{ + { + Kind: "TabsLayoutTab", + Spec: dashv2alpha1.DashboardTabsLayoutTabSpec{ + Title: ptrString("Tab 1"), + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: []dashv2alpha1.DashboardGridLayoutItemKind{ + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel1"}}}, + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel2"}}}, + }, + }, + }, + }, + }, + }, + { + Kind: "TabsLayoutTab", + Spec: dashv2alpha1.DashboardTabsLayoutTabSpec{ + Title: ptrString("Tab 2"), + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: []dashv2alpha1.DashboardGridLayoutItemKind{ + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel3"}}}, + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel4"}}}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +// createV2DashboardWithRowsCollapsed creates a V2 dashboard with RowsLayout (collapsed rows) +// Structure: RowsLayout > Row1(collapsed)[panel1, panel2] + Row2(collapsed)[panel3, panel4] +func createV2DashboardWithRowsCollapsed() *dashv2alpha1.Dashboard { + elements := map[string]dashv2alpha1.DashboardElement{} + for i := 1; i <= 4; i++ { + rowNum := (i-1)/2 + 1 + elements[fmt.Sprintf("panel%d", i)] = dashv2alpha1.DashboardElement{ + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: float64(i), + Title: fmt.Sprintf("Panel %d - Row %d (collapsed)", i, rowNum), + VizConfig: dashv2alpha1.DashboardVizConfigKind{Kind: "timeseries"}, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{ + {Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "A"}}, + }, + }, + }, + }, + }, + } + } + + collapsed := true + return &dashv2alpha1.Dashboard{ + Spec: dashv2alpha1.DashboardSpec{ + Elements: elements, + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + RowsLayoutKind: &dashv2alpha1.DashboardRowsLayoutKind{ + Kind: "RowsLayout", + Spec: dashv2alpha1.DashboardRowsLayoutSpec{ + Rows: []dashv2alpha1.DashboardRowsLayoutRowKind{ + { + Kind: "RowsLayoutRow", + Spec: dashv2alpha1.DashboardRowsLayoutRowSpec{ + Title: ptrString("Row 1 (collapsed)"), + Collapse: &collapsed, + Layout: dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: []dashv2alpha1.DashboardGridLayoutItemKind{ + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel1"}}}, + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel2"}}}, + }, + }, + }, + }, + }, + }, + { + Kind: "RowsLayoutRow", + Spec: dashv2alpha1.DashboardRowsLayoutRowSpec{ + Title: ptrString("Row 2 (collapsed)"), + Collapse: &collapsed, + Layout: dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: []dashv2alpha1.DashboardGridLayoutItemKind{ + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel3"}}}, + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel4"}}}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +// createV2DashboardWithRowsExpanded creates a V2 dashboard with RowsLayout (expanded rows) +// Structure: RowsLayout > Row1(expanded)[panel1, panel2] + Row2(expanded)[panel3, panel4] +func createV2DashboardWithRowsExpanded() *dashv2alpha1.Dashboard { + elements := map[string]dashv2alpha1.DashboardElement{} + for i := 1; i <= 4; i++ { + rowNum := (i-1)/2 + 1 + elements[fmt.Sprintf("panel%d", i)] = dashv2alpha1.DashboardElement{ + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: float64(i), + Title: fmt.Sprintf("Panel %d - Row %d (expanded)", i, rowNum), + VizConfig: dashv2alpha1.DashboardVizConfigKind{Kind: "timeseries"}, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{ + {Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "A"}}, + }, + }, + }, + }, + }, + } + } + + expanded := false // collapse=false means expanded + return &dashv2alpha1.Dashboard{ + Spec: dashv2alpha1.DashboardSpec{ + Elements: elements, + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + RowsLayoutKind: &dashv2alpha1.DashboardRowsLayoutKind{ + Kind: "RowsLayout", + Spec: dashv2alpha1.DashboardRowsLayoutSpec{ + Rows: []dashv2alpha1.DashboardRowsLayoutRowKind{ + { + Kind: "RowsLayoutRow", + Spec: dashv2alpha1.DashboardRowsLayoutRowSpec{ + Title: ptrString("Row 1 (expanded)"), + Collapse: &expanded, + Layout: dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: []dashv2alpha1.DashboardGridLayoutItemKind{ + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel1"}}}, + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel2"}}}, + }, + }, + }, + }, + }, + }, + { + Kind: "RowsLayoutRow", + Spec: dashv2alpha1.DashboardRowsLayoutRowSpec{ + Title: ptrString("Row 2 (expanded)"), + Collapse: &expanded, + Layout: dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: []dashv2alpha1.DashboardGridLayoutItemKind{ + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel3"}}}, + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel4"}}}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func createV0V1FlatPanels(numPanels, queriesPerPanel int) map[string]interface{} { + panels := make([]interface{}, numPanels) + for i := 0; i < numPanels; i++ { + targets := make([]interface{}, queriesPerPanel) + for q := 0; q < queriesPerPanel; q++ { + targets[q] = map[string]interface{}{"refId": fmt.Sprintf("%c", 'A'+q)} + } + panels[i] = map[string]interface{}{ + "id": float64(i + 1), + "title": fmt.Sprintf("Panel %d", i+1), + "type": "timeseries", + "targets": targets, + } + } + return map[string]interface{}{"panels": panels} +} + +func createV0V1WithCollapsedRows(numRows, panelsPerRow, queriesPerPanel int) map[string]interface{} { + panels := make([]interface{}, 0) + panelID := 1 + + for r := 0; r < numRows; r++ { + // Create collapsed panels for this row + collapsedPanels := make([]interface{}, panelsPerRow) + for p := 0; p < panelsPerRow; p++ { + targets := make([]interface{}, queriesPerPanel) + for q := 0; q < queriesPerPanel; q++ { + targets[q] = map[string]interface{}{"refId": fmt.Sprintf("%c", 'A'+q)} + } + collapsedPanels[p] = map[string]interface{}{ + "id": float64(panelID), + "title": fmt.Sprintf("Panel %d (in Row %d)", panelID, r+1), + "type": "timeseries", + "targets": targets, + } + panelID++ + } + + // Add row with collapsed panels + panels = append(panels, map[string]interface{}{ + "id": float64(100 + r), + "title": fmt.Sprintf("Row %d (collapsed)", r+1), + "type": "row", + "collapsed": true, + "panels": collapsedPanels, + }) + } + return map[string]interface{}{"panels": panels} +} + +func createV0V1WithExpandedRows(numRows, panelsPerRow, queriesPerPanel int) map[string]interface{} { + panels := make([]interface{}, 0) + panelID := 1 + + for r := 0; r < numRows; r++ { + // Add expanded row (no nested panels) + panels = append(panels, map[string]interface{}{ + "id": float64(100 + r), + "title": fmt.Sprintf("Row %d (expanded)", r+1), + "type": "row", + // No "panels" field when row is expanded + }) + + // Add panels after the row + for p := 0; p < panelsPerRow; p++ { + targets := make([]interface{}, queriesPerPanel) + for q := 0; q < queriesPerPanel; q++ { + targets[q] = map[string]interface{}{"refId": fmt.Sprintf("%c", 'A'+q)} + } + panels = append(panels, map[string]interface{}{ + "id": float64(panelID), + "title": fmt.Sprintf("Panel %d (after Row %d)", panelID, r+1), + "type": "timeseries", + "targets": targets, + }) + panelID++ + } + } + return map[string]interface{}{"panels": panels} +} + +func createV2DashboardWithTabsAndNestedRows() *dashv2alpha1.Dashboard { + // 2 tabs, each with 1 row containing 2 panels = 4 panels total + // Structure: TabsLayout > Tab1[RowsLayout > Row[panel1, panel2]] + Tab2[RowsLayout > Row[panel3, panel4]] + elements := map[string]dashv2alpha1.DashboardElement{ + "panel1": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 1, Title: "Panel 1 - Tab1/Row1", + VizConfig: dashv2alpha1.DashboardVizConfigKind{Kind: "timeseries"}, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{{Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "A"}}}, + }, + }, + }, + }, + }, + "panel2": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 2, Title: "Panel 2 - Tab1/Row1", + VizConfig: dashv2alpha1.DashboardVizConfigKind{Kind: "stat"}, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{{Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "A"}}}, + }, + }, + }, + }, + }, + "panel3": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 3, Title: "Panel 3 - Tab2/Row1", + VizConfig: dashv2alpha1.DashboardVizConfigKind{Kind: "gauge"}, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{{Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "A"}}}, + }, + }, + }, + }, + }, + "panel4": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 4, Title: "Panel 4 - Tab2/Row1", + VizConfig: dashv2alpha1.DashboardVizConfigKind{Kind: "table"}, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{{Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "A"}}}, + }, + }, + }, + }, + }, + } + + return &dashv2alpha1.Dashboard{ + Spec: dashv2alpha1.DashboardSpec{ + Elements: elements, + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + TabsLayoutKind: &dashv2alpha1.DashboardTabsLayoutKind{ + Kind: "TabsLayout", + Spec: dashv2alpha1.DashboardTabsLayoutSpec{ + Tabs: []dashv2alpha1.DashboardTabsLayoutTabKind{ + { + Kind: "TabsLayoutTab", + Spec: dashv2alpha1.DashboardTabsLayoutTabSpec{ + Title: ptrString("Tab 1"), + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + RowsLayoutKind: &dashv2alpha1.DashboardRowsLayoutKind{ + Kind: "RowsLayout", + Spec: dashv2alpha1.DashboardRowsLayoutSpec{ + Rows: []dashv2alpha1.DashboardRowsLayoutRowKind{ + { + Kind: "RowsLayoutRow", + Spec: dashv2alpha1.DashboardRowsLayoutRowSpec{ + Title: ptrString("Row 1"), + Layout: dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: []dashv2alpha1.DashboardGridLayoutItemKind{ + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel1"}}}, + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel2"}}}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + Kind: "TabsLayoutTab", + Spec: dashv2alpha1.DashboardTabsLayoutTabSpec{ + Title: ptrString("Tab 2"), + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + RowsLayoutKind: &dashv2alpha1.DashboardRowsLayoutKind{ + Kind: "RowsLayout", + Spec: dashv2alpha1.DashboardRowsLayoutSpec{ + Rows: []dashv2alpha1.DashboardRowsLayoutRowKind{ + { + Kind: "RowsLayoutRow", + Spec: dashv2alpha1.DashboardRowsLayoutRowSpec{ + Title: ptrString("Row 1"), + Layout: dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: []dashv2alpha1.DashboardGridLayoutItemKind{ + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel3"}}}, + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel4"}}}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func createV2DashboardWithRowsAndNestedTabs() *dashv2alpha1.Dashboard { + // 2 rows, each containing a tab layout with 2 panels = 4 panels total + // Structure: RowsLayout > Row1[TabsLayout > Tab[panel1, panel2]] + Row2[TabsLayout > Tab[panel3, panel4]] + elements := map[string]dashv2alpha1.DashboardElement{ + "panel1": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 1, Title: "Panel 1 - Row1/TabA", + VizConfig: dashv2alpha1.DashboardVizConfigKind{Kind: "timeseries"}, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{{Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "A"}}}, + }, + }, + }, + }, + }, + "panel2": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 2, Title: "Panel 2 - Row1/TabB", + VizConfig: dashv2alpha1.DashboardVizConfigKind{Kind: "stat"}, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{{Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "A"}}}, + }, + }, + }, + }, + }, + "panel3": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 3, Title: "Panel 3 - Row2/TabA", + VizConfig: dashv2alpha1.DashboardVizConfigKind{Kind: "gauge"}, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{{Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "A"}}}, + }, + }, + }, + }, + }, + "panel4": { + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: 4, Title: "Panel 4 - Row2/TabB", + VizConfig: dashv2alpha1.DashboardVizConfigKind{Kind: "table"}, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{{Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "A"}}}, + }, + }, + }, + }, + }, + } + + return &dashv2alpha1.Dashboard{ + Spec: dashv2alpha1.DashboardSpec{ + Elements: elements, + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + RowsLayoutKind: &dashv2alpha1.DashboardRowsLayoutKind{ + Kind: "RowsLayout", + Spec: dashv2alpha1.DashboardRowsLayoutSpec{ + Rows: []dashv2alpha1.DashboardRowsLayoutRowKind{ + { + Kind: "RowsLayoutRow", + Spec: dashv2alpha1.DashboardRowsLayoutRowSpec{ + Title: ptrString("Row 1"), + Layout: dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind{ + TabsLayoutKind: &dashv2alpha1.DashboardTabsLayoutKind{ + Kind: "TabsLayout", + Spec: dashv2alpha1.DashboardTabsLayoutSpec{ + Tabs: []dashv2alpha1.DashboardTabsLayoutTabKind{ + { + Kind: "TabsLayoutTab", + Spec: dashv2alpha1.DashboardTabsLayoutTabSpec{ + Title: ptrString("Tab A"), + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: []dashv2alpha1.DashboardGridLayoutItemKind{ + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel1"}}}, + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel2"}}}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + Kind: "RowsLayoutRow", + Spec: dashv2alpha1.DashboardRowsLayoutRowSpec{ + Title: ptrString("Row 2"), + Layout: dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind{ + TabsLayoutKind: &dashv2alpha1.DashboardTabsLayoutKind{ + Kind: "TabsLayout", + Spec: dashv2alpha1.DashboardTabsLayoutSpec{ + Tabs: []dashv2alpha1.DashboardTabsLayoutTabKind{ + { + Kind: "TabsLayoutTab", + Spec: dashv2alpha1.DashboardTabsLayoutTabSpec{ + Title: ptrString("Tab A"), + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: []dashv2alpha1.DashboardGridLayoutItemKind{ + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel3"}}}, + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel4"}}}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func createV2ComplexNestedDashboard() *dashv2alpha1.Dashboard { + // Complex: 2 tabs, Tab1 has 2 rows (each with 2 panels), Tab2 has 2 panels directly + // Total: 6 panels + // Structure: TabsLayout > Tab1[RowsLayout > Row1[p1,p2] + Row2[p3,p4]] + Tab2[GridLayout > p5,p6] + elements := map[string]dashv2alpha1.DashboardElement{} + for i := 1; i <= 6; i++ { + elements[fmt.Sprintf("panel%d", i)] = dashv2alpha1.DashboardElement{ + PanelKind: &dashv2alpha1.DashboardPanelKind{ + Kind: "Panel", + Spec: dashv2alpha1.DashboardPanelSpec{ + Id: float64(i), + Title: fmt.Sprintf("Panel %d", i), + VizConfig: dashv2alpha1.DashboardVizConfigKind{Kind: "timeseries"}, + Data: dashv2alpha1.DashboardQueryGroupKind{ + Spec: dashv2alpha1.DashboardQueryGroupSpec{ + Queries: []dashv2alpha1.DashboardPanelQueryKind{{Spec: dashv2alpha1.DashboardPanelQuerySpec{RefId: "A"}}}, + }, + }, + }, + }, + } + } + + return &dashv2alpha1.Dashboard{ + Spec: dashv2alpha1.DashboardSpec{ + Elements: elements, + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + TabsLayoutKind: &dashv2alpha1.DashboardTabsLayoutKind{ + Kind: "TabsLayout", + Spec: dashv2alpha1.DashboardTabsLayoutSpec{ + Tabs: []dashv2alpha1.DashboardTabsLayoutTabKind{ + { + Kind: "TabsLayoutTab", + Spec: dashv2alpha1.DashboardTabsLayoutTabSpec{ + Title: ptrString("Tab 1"), + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + RowsLayoutKind: &dashv2alpha1.DashboardRowsLayoutKind{ + Kind: "RowsLayout", + Spec: dashv2alpha1.DashboardRowsLayoutSpec{ + Rows: []dashv2alpha1.DashboardRowsLayoutRowKind{ + { + Kind: "RowsLayoutRow", + Spec: dashv2alpha1.DashboardRowsLayoutRowSpec{ + Title: ptrString("Row 1"), + Layout: dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: []dashv2alpha1.DashboardGridLayoutItemKind{ + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel1"}}}, + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel2"}}}, + }, + }, + }, + }, + }, + }, + { + Kind: "RowsLayoutRow", + Spec: dashv2alpha1.DashboardRowsLayoutRowSpec{ + Title: ptrString("Row 2"), + Layout: dashv2alpha1.DashboardGridLayoutKindOrAutoGridLayoutKindOrTabsLayoutKindOrRowsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: []dashv2alpha1.DashboardGridLayoutItemKind{ + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel3"}}}, + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel4"}}}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + Kind: "TabsLayoutTab", + Spec: dashv2alpha1.DashboardTabsLayoutTabSpec{ + Title: ptrString("Tab 2"), + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{ + Items: []dashv2alpha1.DashboardGridLayoutItemKind{ + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel5"}}}, + {Spec: dashv2alpha1.DashboardGridLayoutItemSpec{Element: dashv2alpha1.DashboardElementReference{Name: "panel6"}}}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +// createV0V1MixedAllTypes creates a V0/V1 dashboard with ALL layout types: +// - Panels out of rows (flat, top-level) +// - Collapsed row with nested panels +// - Expanded row followed by panels +// Structure: [p1, p2, Row1(collapsed){p3,p4}, Row2(expanded), p5, p6] +// Total: 6 panels +func createV0V1MixedAllTypes() map[string]interface{} { + panels := []interface{}{ + // Flat panels out of rows (at the beginning) + map[string]interface{}{ + "id": float64(1), + "title": "Panel 1 (flat, no row)", + "type": "timeseries", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + map[string]interface{}{ + "id": float64(2), + "title": "Panel 2 (flat, no row)", + "type": "stat", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + + // Collapsed row with nested panels + map[string]interface{}{ + "id": float64(100), + "title": "Row 1 (collapsed)", + "type": "row", + "collapsed": true, + "panels": []interface{}{ + map[string]interface{}{ + "id": float64(3), + "title": "Panel 3 (in collapsed row)", + "type": "gauge", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + map[string]interface{}{ + "id": float64(4), + "title": "Panel 4 (in collapsed row)", + "type": "table", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + }, + }, + + // Expanded row (empty, panels follow after) + map[string]interface{}{ + "id": float64(101), + "title": "Row 2 (expanded)", + "type": "row", + // No "panels" field when row is expanded + }, + + // Panels after expanded row + map[string]interface{}{ + "id": float64(5), + "title": "Panel 5 (after expanded row)", + "type": "bargauge", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + map[string]interface{}{ + "id": float64(6), + "title": "Panel 6 (after expanded row)", + "type": "piechart", + "targets": []interface{}{ + map[string]interface{}{"refId": "A"}, + }, + }, + } + + return map[string]interface{}{"panels": panels} +} + +// ptrString is a helper to create a pointer to a string +func ptrString(s string) *string { + return &s +} diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2alpha1.json index 7a4a4d2c4b7..5fdde6e46be 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2alpha1.json @@ -237,5 +237,10 @@ "title": "V10 Table Thresholds Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2beta1.json index 69d75b09dcf..b2eb0336cf2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2beta1.json @@ -244,5 +244,10 @@ "title": "V10 Table Thresholds Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2alpha1.json index f427b5dbcb7..7185cc96f69 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2alpha1.json @@ -206,5 +206,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2beta1.json index 8e59bbd21ca..2a0200eb4cb 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2beta1.json @@ -213,5 +213,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v2alpha1.json index f55769f82b2..5e69664e182 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v2alpha1.json @@ -203,5 +203,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v2beta1.json index febee713d97..e3d0bff265d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v12.template-variables.v42.v2beta1.json @@ -216,5 +216,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2alpha1.json index a1ef92d3d50..2d55803abbe 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2alpha1.json @@ -351,5 +351,10 @@ "title": "V13 Graph Thresholds Migration Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2beta1.json index a41d04804b7..97fc1bfca44 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2beta1.json @@ -362,5 +362,10 @@ "title": "V13 Graph Thresholds Migration Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2alpha1.json index 3edc16edae6..818011bba5b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2alpha1.json @@ -129,5 +129,10 @@ "title": "Dashboard with minimal graph panel settings", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2beta1.json index 8e18ac60c4d..3c12cff0619 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2beta1.json @@ -132,5 +132,10 @@ "title": "Dashboard with minimal graph panel settings", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2alpha1.json index e4a62f00bc7..4c9617a521e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2alpha1.json @@ -210,5 +210,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2beta1.json index 975fc24c969..bf07de3de24 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2beta1.json @@ -217,5 +217,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2alpha1.json index decdff12902..8d340c0f65e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2alpha1.json @@ -1004,5 +1004,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2beta1.json index 9db5193d5af..8070895766a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2beta1.json @@ -1023,5 +1023,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2alpha1.json index 347556eb45d..cd1daaf4806 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2alpha1.json @@ -223,5 +223,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2beta1.json index 756f4a76074..63a51a69c96 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2beta1.json @@ -231,5 +231,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v2alpha1.json index 1bf45848829..4c21c63a66a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v2alpha1.json @@ -1455,5 +1455,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v2beta1.json index d7f5fd82659..684b7b0f564 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.empty-rows-and-panels-array.v42.v2beta1.json @@ -1481,5 +1481,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json index 6cf8402f377..13e2bd96197 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json @@ -719,5 +719,10 @@ "title": "V16 Grid Layout Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json index 2a6622f27a4..bbd59bf1a07 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json @@ -739,5 +739,10 @@ "title": "V16 Grid Layout Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v2alpha1.json index 221b01ffec5..52c19734101 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v2alpha1.json @@ -1655,5 +1655,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v2beta1.json index 7c05e2ab74c..49e7fb1d9e7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.span_zero_demo.v42.v2beta1.json @@ -1707,5 +1707,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2alpha1.json index aa9017f0720..05de2de2aa4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2alpha1.json @@ -636,5 +636,10 @@ "title": "V17 MinSpan to MaxPerRow Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2beta1.json index 697c0dadaac..778a653435a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2beta1.json @@ -657,5 +657,10 @@ "title": "V17 MinSpan to MaxPerRow Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2alpha1.json index f154e2e2679..d524005a101 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2alpha1.json @@ -401,5 +401,10 @@ "title": "V18 Gauge Options Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2beta1.json index 6d1528a868a..b80c575053c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2beta1.json @@ -412,5 +412,10 @@ "title": "V18 Gauge Options Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2alpha1.json index e51fc93636a..3d78d0167b3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2alpha1.json @@ -434,5 +434,10 @@ "title": "V19 Panel Links Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2beta1.json index 0dbf12ef898..2842123f75e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2beta1.json @@ -447,5 +447,10 @@ "title": "V19 Panel Links Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json index 129ba30c694..b6b4f83cdf7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json @@ -354,5 +354,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json index 0c80ae34850..0b9d3bacd35 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json @@ -365,5 +365,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v2alpha1.json index 95d3f1d8834..1e077553a81 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v2alpha1.json @@ -426,5 +426,10 @@ "title": "V20 Variable Syntax Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v2beta1.json index f5c9e157c75..13327bcd9f6 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v20.variable_syntax_links.v42.v2beta1.json @@ -437,5 +437,10 @@ "title": "V20 Variable Syntax Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2alpha1.json index 0b2251f0dca..170e3c6ccad 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2alpha1.json @@ -401,5 +401,10 @@ "title": "V21 Data Links Series to Field Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2beta1.json index 8c9808adf32..b87e2c2fb9e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2beta1.json @@ -412,5 +412,10 @@ "title": "V21 Data Links Series to Field Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2alpha1.json index dd03601b0ac..28eefba141b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2alpha1.json @@ -123,5 +123,10 @@ "title": "V22 Table Panel Styles Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2beta1.json index fd3dc0b587b..d9de4392f97 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2beta1.json @@ -126,5 +126,10 @@ "title": "V22 Table Panel Styles Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v2alpha1.json index a40797444f4..3e93dbc7a66 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v2alpha1.json @@ -374,5 +374,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v2beta1.json index a97d9a51e2f..7286db67bf2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v23.multi_variable_alignment.v42.v2beta1.json @@ -391,5 +391,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json index c9dd5bffa51..206e53baee2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json @@ -1065,5 +1065,10 @@ "title": "No Title", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json index 3d74b493b8e..9eeeb11b247 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json @@ -1101,5 +1101,10 @@ "title": "No Title", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2alpha1.json index 3375a6bffe4..35c94ad87bc 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2alpha1.json @@ -217,5 +217,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2beta1.json index b873fdea3a3..1cdd7407a2d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2beta1.json @@ -226,5 +226,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2alpha1.json index a135614baba..d15f179ac31 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2alpha1.json @@ -240,5 +240,10 @@ "title": "No Title", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2beta1.json index 378ae69f16a..59aad2fdb0b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2beta1.json @@ -247,5 +247,10 @@ "title": "No Title", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2alpha1.json index 51cdcba85c1..1c1b3d0d39b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2alpha1.json @@ -207,5 +207,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2beta1.json index 3fdcd01a27c..b8596415db8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2beta1.json @@ -211,5 +211,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v2alpha1.json index 54384f8dc06..75831381989 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v2alpha1.json @@ -131,5 +131,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v2beta1.json index 6626b919137..e79b7d1f7d7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.remove_variable_properties.v42.v2beta1.json @@ -134,5 +134,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2alpha1.json index 82fc46be585..78df8e5bf3a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2alpha1.json @@ -393,5 +393,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2beta1.json index 4eff500bef6..f9b06083b63 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2beta1.json @@ -406,5 +406,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2alpha1.json index 0873ad2190c..7fa4e5ae944 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2alpha1.json @@ -622,5 +622,10 @@ "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2beta1.json index 2b7d27024c6..5aaff274c67 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2beta1.json @@ -642,5 +642,10 @@ "title": "V28 Singlestat and Variable Properties Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v2alpha1.json index 2688367b33b..5a096f20011 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v2alpha1.json @@ -435,5 +435,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v2beta1.json index 8e57483ceb4..270c6912485 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v29.query_variables_refresh_and_options.v42.v2beta1.json @@ -458,5 +458,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v2alpha1.json index db33eea5445..112dae69255 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v2alpha1.json @@ -294,5 +294,10 @@ "title": "V3 No-Op Migration - but tests ensuring panel IDs are unique", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v2beta1.json index 4a38725abc3..0a3a97926b8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v3.no-op.v42.v2beta1.json @@ -303,5 +303,10 @@ "title": "V3 No-Op Migration - but tests ensuring panel IDs are unique", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2alpha1.json index 60334f9ffb1..a045836b269 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2alpha1.json @@ -689,5 +689,10 @@ "title": "V30 Value Mappings and Tooltip Options Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2beta1.json index 6848d2d1714..5ae7d7d5ef4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2beta1.json @@ -705,5 +705,10 @@ "title": "V30 Value Mappings and Tooltip Options Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v2alpha1.json index ac34174bfcb..6fe0fcc67c8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v2alpha1.json @@ -669,5 +669,10 @@ "title": "V31 LabelsToFields Merge Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v2beta1.json index d7f0684597c..5ca1fbff96c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v31.labels_to_fields_merge.v42.v2beta1.json @@ -684,5 +684,10 @@ "title": "V31 LabelsToFields Merge Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2alpha1.json index 3c58166f6c4..2042cc9aa49 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2alpha1.json @@ -310,5 +310,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2beta1.json index 1611cea2464..7a90cf0c2ac 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2beta1.json @@ -320,5 +320,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2alpha1.json index d41d2d7bda3..a7cccb454ae 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2alpha1.json @@ -720,5 +720,10 @@ "title": "V33 Panel Datasource Name to Ref Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2beta1.json index fbb8d83b728..55836cf469c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2beta1.json @@ -746,5 +746,10 @@ "title": "V33 Panel Datasource Name to Ref Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v2alpha1.json index ad69702a739..e5308bb6102 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v2alpha1.json @@ -1900,5 +1900,10 @@ "title": "CloudWatch Multiple Statistics Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v2beta1.json index 79ee3db369e..329585edd02 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v34.multiple_stats_cloudwatch.v42.v2beta1.json @@ -1964,5 +1964,10 @@ "title": "CloudWatch Multiple Statistics Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v2alpha1.json index c06dd85bb25..cbb59009f10 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v2alpha1.json @@ -595,5 +595,10 @@ "title": "X-Axis Visibility Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v2beta1.json index f9be2a6cee1..fde3ba9c006 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v35.ensure_x_axis_visibility.v42.v2beta1.json @@ -612,5 +612,10 @@ "title": "X-Axis Visibility Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v2alpha1.json index 474b22d4ffb..97a0dd7d19c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v2alpha1.json @@ -1029,5 +1029,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v2beta1.json index 6465fae7ed0..24c49f553ae 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v36.ds_name_to_ref.v42.v2beta1.json @@ -1065,5 +1065,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2alpha1.json index eb254828d3e..62c7806854a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2alpha1.json @@ -629,5 +629,10 @@ "title": "V37 Legend Normalization Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2beta1.json index 2803cc68947..9e112233023 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2beta1.json @@ -642,5 +642,10 @@ "title": "V37 Legend Normalization Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2alpha1.json index 92f6ec2ef8e..dd8087721a9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2alpha1.json @@ -659,5 +659,10 @@ "title": "V38 Table Migration Comprehensive Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2beta1.json index 493b6147404..3a75ab8b887 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2beta1.json @@ -671,5 +671,10 @@ "title": "V38 Table Migration Comprehensive Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2alpha1.json index a1fb5ca81af..f7ed9a945bf 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2alpha1.json @@ -659,5 +659,10 @@ "title": "V38 Table Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2beta1.json index 81cc875df07..f8eb9ef3616 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2beta1.json @@ -671,5 +671,10 @@ "title": "V38 Table Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2alpha1.json index 37eb51fbdff..368f059af62 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2alpha1.json @@ -586,5 +586,10 @@ "title": "V39 TimeSeriesTable Transformation Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2beta1.json index 3b56960e026..99de7609d9a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2beta1.json @@ -596,5 +596,10 @@ "title": "V39 TimeSeriesTable Transformation Migration Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2alpha1.json index e6701e601c7..983b06ebf43 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2alpha1.json @@ -237,5 +237,10 @@ "title": "V4 No-Op Migration Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2beta1.json index b6d1aa7d200..4e523705cb0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2beta1.json @@ -244,5 +244,10 @@ "title": "V4 No-Op Migration Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v2alpha1.json index 10f6bbf20f4..40df255245e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v2alpha1.json @@ -64,5 +64,10 @@ "title": "Empty String Refresh Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v2beta1.json index a6cabe62d43..44136818b17 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_empty_string.v42.v2beta1.json @@ -65,5 +65,10 @@ "title": "Empty String Refresh Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v2alpha1.json index 13a901e94af..e149f9f6a45 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v2alpha1.json @@ -64,5 +64,10 @@ "title": "Boolean False Refresh Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v2beta1.json index 7b5f900ef36..0499fbc7328 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_false.v42.v2beta1.json @@ -65,5 +65,10 @@ "title": "Boolean False Refresh Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v2alpha1.json index 04b94fc3919..95b61ef4e22 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v2alpha1.json @@ -64,5 +64,10 @@ "title": "Refresh Not Set Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v2beta1.json index 1f09949f995..93ce85b17e1 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_not_set.v42.v2beta1.json @@ -65,5 +65,10 @@ "title": "Refresh Not Set Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v2alpha1.json index 103fe15fdf3..fd250ce03c6 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v2alpha1.json @@ -64,5 +64,10 @@ "title": "Numeric Refresh Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v2beta1.json index 6904267e72b..f81e1b004e9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_numeric.v42.v2beta1.json @@ -65,5 +65,10 @@ "title": "Numeric Refresh Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v2alpha1.json index 9fe42b778dd..94fa361257d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v2alpha1.json @@ -64,5 +64,10 @@ "title": "String Refresh Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v2beta1.json index b1bd81269c1..f8818a9dbf3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_string.v42.v2beta1.json @@ -65,5 +65,10 @@ "title": "String Refresh Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v2alpha1.json index 3524796f0aa..bd9f897d313 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v2alpha1.json @@ -64,5 +64,10 @@ "title": "Boolean Refresh Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v2beta1.json index 76eea7dbbdc..eecd119a617 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v40.refresh_true.v42.v2beta1.json @@ -65,5 +65,10 @@ "title": "Boolean Refresh Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v2alpha1.json index 9036e54f22d..ed1ea8dc001 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v2alpha1.json @@ -64,5 +64,10 @@ "title": "No Time Picker Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v2beta1.json index 54e2cfeef43..6b3b49c11e5 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.no_time_picker.v42.v2beta1.json @@ -65,5 +65,10 @@ "title": "No Time Picker Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v2alpha1.json index 78e27ac4411..2806b7c6d8d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v2alpha1.json @@ -64,5 +64,10 @@ "title": "Time Picker No Time Options Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v2beta1.json index 52de1909de4..5b9595d8f74 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_no_time_options.v42.v2beta1.json @@ -65,5 +65,10 @@ "title": "Time Picker No Time Options Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v2alpha1.json index 8115f687203..a48695c53ec 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v2alpha1.json @@ -64,5 +64,10 @@ "title": "Time Picker Time Options Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v2beta1.json index 9dffb1fc27e..3f31b897bf0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v41.time_picker_time_options.v42.v2beta1.json @@ -65,5 +65,10 @@ "title": "Time Picker Time Options Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v2alpha1.json index ee97abbd7e2..6aa423b4431 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v2alpha1.json @@ -123,5 +123,10 @@ "title": "v42 Migration Test - Harky Must", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v2beta1.json index 3d267c3135b..79648901fc2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.harky_must.v42.v2beta1.json @@ -125,5 +125,10 @@ "title": "v42 Migration Test - Harky Must", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2alpha1.json index d231c6561ad..3474345415f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2alpha1.json @@ -461,5 +461,10 @@ "title": "v42 Migration Test - HideFrom Tooltip", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2beta1.json index a1e187676a5..6f0e6b08043 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2beta1.json @@ -468,5 +468,10 @@ "title": "v42 Migration Test - HideFrom Tooltip", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2alpha1.json index 3c5f977cf19..d39da494ad0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2alpha1.json @@ -237,5 +237,10 @@ "title": "V5 No-Op Migration Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2beta1.json index 10544c3c0fe..4795fc07dc5 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2beta1.json @@ -244,5 +244,10 @@ "title": "V5 No-Op Migration Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2alpha1.json index 5c98e01c0f0..60ef3b2ec90 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2alpha1.json @@ -332,5 +332,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2beta1.json index 19fbf9be88f..3396e17655d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2beta1.json @@ -344,5 +344,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v2alpha1.json index 425516baea9..10b57eb4b69 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v2alpha1.json @@ -142,5 +142,10 @@ "title": "No Title", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v2beta1.json index 72cb9aadb9b..252a510f233 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v7.timepicker.v42.v2beta1.json @@ -146,5 +146,10 @@ "title": "No Title", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2alpha1.json index 0bf433b534a..7b4432f77b5 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2alpha1.json @@ -247,5 +247,10 @@ "title": "V8 InfluxDB Query Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2beta1.json index d8ee1076605..40d7695e10c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2beta1.json @@ -252,5 +252,10 @@ "title": "V8 InfluxDB Query Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2alpha1.json index 0e4ea43800d..e39200fdc8e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2alpha1.json @@ -237,5 +237,10 @@ "title": "V9 No-Op Migration Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2beta1.json index 45487aaebb9..71ba1439de3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2beta1.json @@ -244,5 +244,10 @@ "title": "V9 No-Op Migration Test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.annotation-filtering.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.annotation-filtering.v42.v2alpha1.json index 59ed9c457a1..c7f21881b4c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.annotation-filtering.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.annotation-filtering.v42.v2alpha1.json @@ -558,5 +558,10 @@ "title": "Annotation filtering", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.annotation-filtering.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.annotation-filtering.v42.v2beta1.json index 26e6d792f59..b7a613fc48b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.annotation-filtering.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.annotation-filtering.v42.v2beta1.json @@ -570,5 +570,10 @@ "title": "Annotation filtering", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2alpha1.json index 5449d222a2c..b6addcc81ed 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2alpha1.json @@ -1368,5 +1368,10 @@ "title": "Multi-lane annotations", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2beta1.json index e23c11c69ef..f806e27a98f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/annotations/v0alpha1.multi-lane-annotations.v42.v2beta1.json @@ -1393,5 +1393,10 @@ "title": "Multi-lane annotations", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_complex.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_complex.v42.v2alpha1.json index 253b6471087..5fb32832089 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_complex.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_complex.v42.v2alpha1.json @@ -7244,5 +7244,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_complex.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_complex.v42.v2beta1.json index 54edeecb540..3ab6bb18228 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_complex.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_complex.v42.v2beta1.json @@ -7345,5 +7345,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_migration.v42.v2alpha1.json index 5b384c10afc..f2cd5feadf1 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_migration.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_migration.v42.v2alpha1.json @@ -13081,5 +13081,10 @@ "title": "Datasource tests - Elasticsearch migration", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_migration.v42.v2beta1.json index 898dc2b2489..5ba04ac7b23 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_migration.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_migration.v42.v2beta1.json @@ -13289,5 +13289,10 @@ "title": "Datasource tests - Elasticsearch migration", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_simple.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_simple.v42.v2alpha1.json index 9eab3b48079..7cf034e5479 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_simple.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_simple.v42.v2alpha1.json @@ -1450,5 +1450,10 @@ "title": "Datasource tests - Elasticsearch simple", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_simple.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_simple.v42.v2beta1.json index 35e24952e8a..e358643fc42 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_simple.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-elasticsearch/v0alpha1.elasticsearch_simple.v42.v2beta1.json @@ -1472,5 +1472,10 @@ "title": "Datasource tests - Elasticsearch simple", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2alpha1.json index 462481754b0..0ed400e5163 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2alpha1.json @@ -239,5 +239,10 @@ "title": "Datasource tests - InfluxDB Logs", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2beta1.json index 7cfd839bf00..4bb33313829 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2beta1.json @@ -245,5 +245,10 @@ "title": "Datasource tests - InfluxDB Logs", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2alpha1.json index 3b776907f83..63037588772 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2alpha1.json @@ -317,5 +317,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2beta1.json index a6ebf10b8f0..19495974932 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2beta1.json @@ -327,5 +327,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_fakedata.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_fakedata.v42.v2alpha1.json index d8a362c5029..f6e20264866 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_fakedata.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_fakedata.v42.v2alpha1.json @@ -615,5 +615,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_fakedata.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_fakedata.v42.v2beta1.json index 029c2bcf218..01d2f669fe1 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_fakedata.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_fakedata.v42.v2beta1.json @@ -627,5 +627,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_query_splitting.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_query_splitting.v42.v2alpha1.json index e5078c03a19..c3feede3aca 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_query_splitting.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_query_splitting.v42.v2alpha1.json @@ -1552,5 +1552,10 @@ "title": "Datasource tests - Loki query splitting", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_query_splitting.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_query_splitting.v42.v2beta1.json index c2651fc75a2..6ac4f48fde4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_query_splitting.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-loki/v0alpha1.loki_query_splitting.v42.v2beta1.json @@ -1577,5 +1577,10 @@ "title": "Datasource tests - Loki query splitting", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2alpha1.json index 3faa6bf6d7b..d7e3a8f61b9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2alpha1.json @@ -546,5 +546,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2beta1.json index b9acf971b1d..2d66d6ca855 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2beta1.json @@ -566,5 +566,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2alpha1.json index 915a7a7f790..69c1c822020 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2alpha1.json @@ -2280,5 +2280,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2beta1.json index f28e28bdded..5ddf1d701a7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2beta1.json @@ -2360,5 +2360,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2alpha1.json index bd3a41d6f40..6dc4619db1d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2alpha1.json @@ -548,5 +548,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2beta1.json index fbb9f11c316..ff02a8b9af0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2beta1.json @@ -568,5 +568,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2alpha1.json index 007f6f3f04c..631815cbc3f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2alpha1.json @@ -2123,5 +2123,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2beta1.json index 35767416230..6ab6536924f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2beta1.json @@ -2197,5 +2197,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2alpha1.json index 71e90cc1bac..7f3926d803e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2alpha1.json @@ -194,5 +194,10 @@ "title": "Datasource tests - OpenTSDB", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2beta1.json index 9566977e001..2959bf4ca8d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2beta1.json @@ -200,5 +200,10 @@ "title": "Datasource tests - OpenTSDB", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2alpha1.json index ccd7b7cac51..03a415d2b4d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2alpha1.json @@ -219,5 +219,10 @@ "title": "Datasource tests - OpenTSDB v2.3", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2beta1.json index dd46ba3407a..d3b15a72cf9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2beta1.json @@ -225,5 +225,10 @@ "title": "Datasource tests - OpenTSDB v2.3", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2alpha1.json index dd5972aef39..15b1ad08458 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2alpha1.json @@ -590,5 +590,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2beta1.json index 707c89428e3..d3572facb36 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2beta1.json @@ -610,5 +610,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2alpha1.json index 1b70b5d07eb..2ac583af244 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2alpha1.json @@ -2121,5 +2121,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2beta1.json index 7878e91dac5..9a9dea7f6f3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2beta1.json @@ -2195,5 +2195,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.bar-gauge-demo2.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.bar-gauge-demo2.v42.v2alpha1.json index 58f0b2cf4df..d7dded5b99f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.bar-gauge-demo2.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.bar-gauge-demo2.v42.v2alpha1.json @@ -1371,5 +1371,10 @@ "title": "Bar Gauge Demo Unfilled", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.bar-gauge-demo2.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.bar-gauge-demo2.v42.v2beta1.json index b1046fe5ca3..f1d552852e8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.bar-gauge-demo2.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.bar-gauge-demo2.v42.v2beta1.json @@ -1434,5 +1434,10 @@ "title": "Bar Gauge Demo Unfilled", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.demo1.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.demo1.v42.v2alpha1.json index 9f0da1593eb..d31a3f25ea2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.demo1.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.demo1.v42.v2alpha1.json @@ -1698,5 +1698,10 @@ "title": "TestData - Demo Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.demo1.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.demo1.v42.v2beta1.json index 3e11006d1de..74943a89cf9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.demo1.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.demo1.v42.v2beta1.json @@ -1745,5 +1745,10 @@ "title": "TestData - Demo Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v74.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v74.v42.v2alpha1.json index ca657342364..e557b4f80a6 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v74.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v74.v42.v2alpha1.json @@ -2880,5 +2880,10 @@ "title": "New Features in v7.4", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v74.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v74.v42.v2beta1.json index 0c8b7143a91..0ad4e83973b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v74.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v74.v42.v2beta1.json @@ -2955,5 +2955,10 @@ "title": "New Features in v7.4", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v8.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v8.v42.v2alpha1.json index dcfd34332f4..ad6e731fd8d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v8.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v8.v42.v2alpha1.json @@ -3951,5 +3951,10 @@ "title": "New Features in v8.0", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v8.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v8.v42.v2beta1.json index 93b501b2222..4f087f02750 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v8.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-testdata/v0alpha1.new_features_in_v8.v42.v2beta1.json @@ -4072,5 +4072,10 @@ "title": "New Features in v8.0", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-Kitchen-Sink.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-Kitchen-Sink.v42.v2alpha1.json index 28ea4680f2e..160c0cdf458 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-Kitchen-Sink.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-Kitchen-Sink.v42.v2alpha1.json @@ -673,5 +673,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-Kitchen-Sink.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-Kitchen-Sink.v42.v2beta1.json index af64f4a2f02..8d1436def13 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-Kitchen-Sink.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-Kitchen-Sink.v42.v2beta1.json @@ -687,5 +687,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-horizontally.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-horizontally.v42.v2alpha1.json index 5f7f66dd3cc..1629c425da7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-horizontally.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-horizontally.v42.v2alpha1.json @@ -261,5 +261,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-horizontally.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-horizontally.v42.v2beta1.json index a4681683ba5..fd375b98fed 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-horizontally.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-horizontally.v42.v2beta1.json @@ -263,5 +263,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-vertically.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-vertically.v42.v2alpha1.json index f8f9922016b..dde9ab81f9c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-vertically.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-vertically.v42.v2alpha1.json @@ -269,5 +269,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-vertically.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-vertically.v42.v2beta1.json index 96c5247c4f4..04266fae41b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-vertically.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-panel-vertically.v42.v2beta1.json @@ -274,5 +274,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-horizontal-panel.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-horizontal-panel.v42.v2alpha1.json index e7e365773f1..f1ef5ec1c11 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-horizontal-panel.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-horizontal-panel.v42.v2alpha1.json @@ -326,5 +326,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-horizontal-panel.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-horizontal-panel.v42.v2beta1.json index 8cb12e2e45f..8344ab2fd3e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-horizontal-panel.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-horizontal-panel.v42.v2beta1.json @@ -331,5 +331,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-vertical-panel.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-vertical-panel.v42.v2alpha1.json index 62c2ef1f9bc..b3c25fda752 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-vertical-panel.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-vertical-panel.v42.v2alpha1.json @@ -326,5 +326,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-vertical-panel.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-vertical-panel.v42.v2beta1.json index 4e7764ff205..facb775e9f1 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-vertical-panel.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-a-row-with-a-repeating-vertical-panel.v42.v2beta1.json @@ -331,5 +331,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-an-empty-row.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-an-empty-row.v42.v2alpha1.json index cc3254f517b..f4a9ccc966a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-an-empty-row.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-an-empty-row.v42.v2alpha1.json @@ -210,5 +210,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-an-empty-row.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-an-empty-row.v42.v2beta1.json index df7016f1969..79c21a744ec 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-an-empty-row.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/e2e-repeats/v0alpha1.Repeating-an-empty-row.v42.v2beta1.json @@ -212,5 +212,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-onclick-extensions.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-onclick-extensions.v42.v2alpha1.json index 3d8e275556b..d3e309f9d05 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-onclick-extensions.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-onclick-extensions.v42.v2alpha1.json @@ -469,5 +469,10 @@ "title": "Link Extensions (onClick)", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-onclick-extensions.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-onclick-extensions.v42.v2beta1.json index a783ef609f2..2d1d0f2e529 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-onclick-extensions.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-onclick-extensions.v42.v2beta1.json @@ -482,5 +482,10 @@ "title": "Link Extensions (onClick)", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-path-extensions.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-path-extensions.v42.v2alpha1.json index 98e0b3fd047..fc48dd77960 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-path-extensions.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-path-extensions.v42.v2alpha1.json @@ -327,5 +327,10 @@ "title": "Link Extensions (path)", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-path-extensions.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-path-extensions.v42.v2beta1.json index 5376f4e3832..8c998ee4b2c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-path-extensions.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/extensions/v0alpha1.link-path-extensions.v42.v2beta1.json @@ -333,5 +333,10 @@ "title": "Link Extensions (path)", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.datadata-macros.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.datadata-macros.v42.v2alpha1.json index 2f913de363f..1ed897f3cc8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.datadata-macros.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.datadata-macros.v42.v2alpha1.json @@ -1167,5 +1167,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.datadata-macros.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.datadata-macros.v42.v2beta1.json index 614ac4cabec..40cc11e8cb9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.datadata-macros.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.datadata-macros.v42.v2beta1.json @@ -1201,5 +1201,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.global-variables-and-interpolation.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.global-variables-and-interpolation.v42.v2alpha1.json index 6b40487f1d0..2b16e9ce76b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.global-variables-and-interpolation.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.global-variables-and-interpolation.v42.v2alpha1.json @@ -168,5 +168,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.global-variables-and-interpolation.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.global-variables-and-interpolation.v42.v2beta1.json index 0bd988b9902..59e7b0a96bc 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.global-variables-and-interpolation.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.global-variables-and-interpolation.v42.v2beta1.json @@ -172,5 +172,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-dashboard-links-and-variables.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-dashboard-links-and-variables.v42.v2alpha1.json index abab61bd552..7480afb75db 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-dashboard-links-and-variables.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-dashboard-links-and-variables.v42.v2alpha1.json @@ -205,5 +205,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-dashboard-links-and-variables.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-dashboard-links-and-variables.v42.v2beta1.json index 4fc9cedb62a..cd93214ebc3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-dashboard-links-and-variables.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-dashboard-links-and-variables.v42.v2beta1.json @@ -209,5 +209,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-panels.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-panels.v42.v2alpha1.json index 2d423c83c3d..f8f7fbc5350 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-panels.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-panels.v42.v2alpha1.json @@ -565,5 +565,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-panels.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-panels.v42.v2beta1.json index 2be4b39a06f..897abd7a0c7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-panels.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-panels.v42.v2beta1.json @@ -574,5 +574,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-rows.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-rows.v42.v2alpha1.json index 17ee9515ded..85cb38c13ff 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-rows.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-rows.v42.v2alpha1.json @@ -485,5 +485,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-rows.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-rows.v42.v2beta1.json index fbe90caa7bd..a1ec03cd26a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-rows.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-repeating-rows.v42.v2beta1.json @@ -491,5 +491,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-textbox-e2e-scenarios.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-textbox-e2e-scenarios.v42.v2alpha1.json index 05b4c6ed70c..123f410cac0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-textbox-e2e-scenarios.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-textbox-e2e-scenarios.v42.v2alpha1.json @@ -136,5 +136,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-textbox-e2e-scenarios.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-textbox-e2e-scenarios.v42.v2beta1.json index c9ba98c312b..0f3f2ab00a2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-textbox-e2e-scenarios.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.templating-textbox-e2e-scenarios.v42.v2beta1.json @@ -140,5 +140,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json index 16148c1ab23..36419c8e892 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json @@ -579,5 +579,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json index 1f8d4d5eee6..2450d74b2f2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json @@ -592,5 +592,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json index 171b505001e..710a4bc0280 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json @@ -334,5 +334,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json index 4b2a7a98790..d93a4dca40f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json @@ -349,5 +349,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json index 837c5cbfa5a..1fb39d454b9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json @@ -639,5 +639,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json index 2b61b379a04..ed73f35670a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json @@ -661,5 +661,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-test-variable-output.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-test-variable-output.v42.v2alpha1.json index efa298e1ec2..9eaf955bb68 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-test-variable-output.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-test-variable-output.v42.v2alpha1.json @@ -247,5 +247,10 @@ "title": "Test variable output", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-test-variable-output.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-test-variable-output.v42.v2beta1.json index 17fa40a073b..395867814e0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-test-variable-output.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-test-variable-output.v42.v2beta1.json @@ -255,5 +255,10 @@ "title": "Test variable output", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2alpha1.json index 8ae6fb9f2cd..3b34a0fd26e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2alpha1.json @@ -259,5 +259,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2beta1.json index b07bdc72f60..6379e2a3191 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2beta1.json @@ -265,5 +265,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2alpha1.json index 08f14e2614f..67579b40d8c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2alpha1.json @@ -198,5 +198,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2beta1.json index a1ff094b67b..d7c1db61b49 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2beta1.json @@ -207,5 +207,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey-refresh.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey-refresh.v42.v2alpha1.json index f71a17e660d..83c4dd25a5c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey-refresh.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey-refresh.v42.v2alpha1.json @@ -536,5 +536,10 @@ "title": "Live flakey stream (w/ liveNow)", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey-refresh.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey-refresh.v42.v2beta1.json index 892812351b8..89bea6fc4d4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey-refresh.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey-refresh.v42.v2beta1.json @@ -545,5 +545,10 @@ "title": "Live flakey stream (w/ liveNow)", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey.v42.v2alpha1.json index abf4e4cdcef..017171df8ef 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey.v42.v2alpha1.json @@ -536,5 +536,10 @@ "title": "Live flakey stream", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey.v42.v2beta1.json index 68c0083c4ab..32cec1120c6 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-flakey.v42.v2beta1.json @@ -545,5 +545,10 @@ "title": "Live flakey stream", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-publish.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-publish.v42.v2alpha1.json index f3777d0eedd..ca4649e18a8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-publish.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-publish.v42.v2alpha1.json @@ -707,5 +707,10 @@ "title": "Live publish test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-publish.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-publish.v42.v2beta1.json index 66e94e73744..e87b98713e2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-publish.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-publish.v42.v2beta1.json @@ -720,5 +720,10 @@ "title": "Live publish test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-streams.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-streams.v42.v2alpha1.json index efe66b835b3..ebe0f2098d8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-streams.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-streams.v42.v2alpha1.json @@ -1034,5 +1034,10 @@ "title": "Live streaming examples", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-streams.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-streams.v42.v2beta1.json index 5ad3b53ea98..051a06f0f94 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-streams.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/live/v0alpha1.live-streams.v42.v2beta1.json @@ -1054,5 +1054,10 @@ "title": "Live streaming examples", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json index f8826a3a699..2ad3f371914 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json @@ -1469,5 +1469,10 @@ "title": "Devenv - Panel migrations", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json index 8eea5775903..5e8d1db63c1 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json @@ -1520,5 +1520,10 @@ "title": "Devenv - Panel migrations", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-autosizing.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-autosizing.v42.v2alpha1.json index 78f2a2b6030..058a8806a5e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-autosizing.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-autosizing.v42.v2alpha1.json @@ -859,5 +859,10 @@ "title": "BarChart - Panel Tests - Value sizing", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-autosizing.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-autosizing.v42.v2beta1.json index c60ab8343a6..6c3fa8ae1a9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-autosizing.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-autosizing.v42.v2beta1.json @@ -880,5 +880,10 @@ "title": "BarChart - Panel Tests - Value sizing", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-label-rotation-skipping.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-label-rotation-skipping.v42.v2alpha1.json index 8f969cbef36..0f77a66f203 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-label-rotation-skipping.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-label-rotation-skipping.v42.v2alpha1.json @@ -428,5 +428,10 @@ "title": "BarChart - Label Rotation \u0026 Skipping", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-label-rotation-skipping.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-label-rotation-skipping.v42.v2beta1.json index 9290dd4ff14..3ad88c72861 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-label-rotation-skipping.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-label-rotation-skipping.v42.v2beta1.json @@ -437,5 +437,10 @@ "title": "BarChart - Label Rotation \u0026 Skipping", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-series-toggle.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-series-toggle.v42.v2alpha1.json index 522857ab19a..8bc7997b3d4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-series-toggle.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-series-toggle.v42.v2alpha1.json @@ -535,5 +535,10 @@ "title": "BarChart - Panel Tests - Series toggle / bar widths", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-series-toggle.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-series-toggle.v42.v2beta1.json index 9459814d20b..7a04ed00d31 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-series-toggle.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-series-toggle.v42.v2beta1.json @@ -548,5 +548,10 @@ "title": "BarChart - Panel Tests - Series toggle / bar widths", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-thresholds-mappings.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-thresholds-mappings.v42.v2alpha1.json index f25d5355ccc..e5369a162f7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-thresholds-mappings.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-thresholds-mappings.v42.v2alpha1.json @@ -1682,5 +1682,10 @@ "title": "BarChart - Thresholds \u0026 Mappings", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-thresholds-mappings.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-thresholds-mappings.v42.v2beta1.json index 0d8c2dbf2a1..208c4c112c0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-thresholds-mappings.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-thresholds-mappings.v42.v2beta1.json @@ -1721,5 +1721,10 @@ "title": "BarChart - Thresholds \u0026 Mappings", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-tooltips-legends.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-tooltips-legends.v42.v2alpha1.json index db478ad7e3d..bd95597bf13 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-tooltips-legends.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-tooltips-legends.v42.v2alpha1.json @@ -1127,5 +1127,10 @@ "title": "Panel Tests - Bar Chart Tooltips \u0026 Legends", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-tooltips-legends.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-tooltips-legends.v42.v2beta1.json index 4ea638cf0eb..3109835cd91 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-tooltips-legends.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-barchart/v0alpha1.barchart-tooltips-legends.v42.v2beta1.json @@ -1155,5 +1155,10 @@ "title": "Panel Tests - Bar Chart Tooltips \u0026 Legends", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.bar_gauge_demo.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.bar_gauge_demo.v42.v2alpha1.json index 0e540c7e66e..290ee546192 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.bar_gauge_demo.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.bar_gauge_demo.v42.v2alpha1.json @@ -1412,5 +1412,10 @@ "title": "Bar Gauge Demo", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.bar_gauge_demo.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.bar_gauge_demo.v42.v2beta1.json index cc190aaa2e0..ebfa6d31425 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.bar_gauge_demo.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.bar_gauge_demo.v42.v2beta1.json @@ -1548,5 +1548,10 @@ "title": "Bar Gauge Demo", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge.v42.v2alpha1.json index c88ce3b28e1..bd408ee62ee 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge.v42.v2alpha1.json @@ -1922,5 +1922,10 @@ "title": "Panel Tests - Bar Gauge", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge.v42.v2beta1.json index 1aa4b0e1a85..cc604bc2045 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge.v42.v2beta1.json @@ -2052,5 +2052,10 @@ "title": "Panel Tests - Bar Gauge", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge2.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge2.v42.v2alpha1.json index 4f782695385..2aa2d935923 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge2.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge2.v42.v2alpha1.json @@ -1214,5 +1214,10 @@ "title": "Panel Tests - Bar Gauge 2", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge2.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge2.v42.v2beta1.json index 777a64366ba..92be6f0c11c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge2.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-bargauge/v0alpha1.panel_tests_bar_gauge2.v42.v2beta1.json @@ -1262,5 +1262,10 @@ "title": "Panel Tests - Bar Gauge 2", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-candlestick/v0alpha1.candlestick.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-candlestick/v0alpha1.candlestick.v42.v2alpha1.json index 97283f755fb..6d92fbd46bb 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-candlestick/v0alpha1.candlestick.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-candlestick/v0alpha1.candlestick.v42.v2alpha1.json @@ -640,5 +640,10 @@ "title": "Candlestick", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-candlestick/v0alpha1.candlestick.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-candlestick/v0alpha1.candlestick.v42.v2beta1.json index 83ab2dd5571..0ce4c776fb5 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-candlestick/v0alpha1.candlestick.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-candlestick/v0alpha1.candlestick.v42.v2beta1.json @@ -653,5 +653,10 @@ "title": "Candlestick", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-connection-examples.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-connection-examples.v42.v2alpha1.json index f5cb9c3c909..2976d37dc0f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-connection-examples.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-connection-examples.v42.v2alpha1.json @@ -4064,5 +4064,10 @@ "title": "Panel Tests - Canvas Connection Examples", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-connection-examples.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-connection-examples.v42.v2beta1.json index 49910daa683..47fb71205e2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-connection-examples.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-connection-examples.v42.v2beta1.json @@ -4074,5 +4074,10 @@ "title": "Panel Tests - Canvas Connection Examples", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-datalinks.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-datalinks.v42.v2alpha1.json index e5648af2325..c9e5b0662df 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-datalinks.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-datalinks.v42.v2alpha1.json @@ -3503,5 +3503,10 @@ "title": "Panel Tests - Canvas Datalinks", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-datalinks.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-datalinks.v42.v2beta1.json index 8f705b659b6..d63bb93d321 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-datalinks.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-datalinks.v42.v2beta1.json @@ -3506,5 +3506,10 @@ "title": "Panel Tests - Canvas Datalinks", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-examples.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-examples.v42.v2alpha1.json index b32f594a99e..8d2bdc0d36c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-examples.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-examples.v42.v2alpha1.json @@ -4096,5 +4096,10 @@ "title": "Panel Tests - Canvas Examples", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-examples.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-examples.v42.v2beta1.json index aa7b95b6dca..0c650fd4f87 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-examples.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-canvas/v0alpha1.canvas-examples.v42.v2beta1.json @@ -4113,5 +4113,10 @@ "title": "Panel Tests - Canvas Examples", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2alpha1.json index 7d0174aecaa..1cb5a102169 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2alpha1.json @@ -871,5 +871,10 @@ "title": "Panel Tests - Auto Decimals", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2beta1.json index c2083797bdf..6def9ba580b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2beta1.json @@ -899,5 +899,10 @@ "title": "Panel Tests - Auto Decimals", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.color_modes.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.color_modes.v42.v2alpha1.json index 12d4efa68b6..bcb9fba2fe9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.color_modes.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.color_modes.v42.v2alpha1.json @@ -471,5 +471,10 @@ "title": "Gradient Color modes", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.color_modes.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.color_modes.v42.v2beta1.json index 3c1e8803dda..54a0b0d198d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.color_modes.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.color_modes.v42.v2beta1.json @@ -481,5 +481,10 @@ "title": "Gradient Color modes", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2alpha1.json index 379d5597285..e394514efd5 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2alpha1.json @@ -2073,5 +2073,10 @@ "title": "Lazy Loading", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2beta1.json index 2767a76315e..eb82d064496 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2beta1.json @@ -2229,5 +2229,10 @@ "title": "Lazy Loading", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.linked-viz.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.linked-viz.v42.v2alpha1.json index 03cea1fc2c8..ce9d8e17187 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.linked-viz.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.linked-viz.v42.v2alpha1.json @@ -1843,5 +1843,10 @@ "title": "Panel \u0026 data links in stat, gauge and bargauge", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.linked-viz.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.linked-viz.v42.v2beta1.json index 6fbdb415483..fe5fbfda4aa 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.linked-viz.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.linked-viz.v42.v2beta1.json @@ -1881,5 +1881,10 @@ "title": "Panel \u0026 data links in stat, gauge and bargauge", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2alpha1.json index 5723bef32ca..d759c27c39b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2alpha1.json @@ -1107,5 +1107,10 @@ "title": "Panel Tests - With \u0026 Without title", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2beta1.json index 93173c86f2b..1143716a2b3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2beta1.json @@ -1137,5 +1137,10 @@ "title": "Panel Tests - With \u0026 Without title", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.shared_queries.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.shared_queries.v42.v2alpha1.json index 15f76709e5e..0c5ddf56280 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.shared_queries.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.shared_queries.v42.v2alpha1.json @@ -614,5 +614,10 @@ "title": "Datasource tests - Shared Queries", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.shared_queries.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.shared_queries.v42.v2beta1.json index be9740c4b92..15ca6962fc8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.shared_queries.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.shared_queries.v42.v2beta1.json @@ -630,5 +630,10 @@ "title": "Datasource tests - Shared Queries", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-dashlist/v0alpha1.dashlist.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-dashlist/v0alpha1.dashlist.v42.v2alpha1.json index d28a40a68f1..08343d13d55 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-dashlist/v0alpha1.dashlist.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-dashlist/v0alpha1.dashlist.v42.v2alpha1.json @@ -139,5 +139,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-dashlist/v0alpha1.dashlist.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-dashlist/v0alpha1.dashlist.v42.v2beta1.json index aedaf24d379..aaf9456f795 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-dashlist/v0alpha1.dashlist.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-dashlist/v0alpha1.dashlist.v42.v2beta1.json @@ -141,5 +141,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-datagrid/v0alpha1.datagrid_metric_values.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-datagrid/v0alpha1.datagrid_metric_values.v42.v2alpha1.json index 7b820200d04..1a01292a17e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-datagrid/v0alpha1.datagrid_metric_values.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-datagrid/v0alpha1.datagrid_metric_values.v42.v2alpha1.json @@ -138,5 +138,10 @@ "title": "Datagrid example", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-datagrid/v0alpha1.datagrid_metric_values.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-datagrid/v0alpha1.datagrid_metric_values.v42.v2beta1.json index ec12c171f3a..fc5049c6d79 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-datagrid/v0alpha1.datagrid_metric_values.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-datagrid/v0alpha1.datagrid_metric_values.v42.v2beta1.json @@ -144,5 +144,10 @@ "title": "Datagrid example", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-flamegraph/v0alpha1.panel_tests_flame_graph.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-flamegraph/v0alpha1.panel_tests_flame_graph.v42.v2alpha1.json index 2ca31f3255f..69033e3dfc5 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-flamegraph/v0alpha1.panel_tests_flame_graph.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-flamegraph/v0alpha1.panel_tests_flame_graph.v42.v2alpha1.json @@ -130,5 +130,10 @@ "title": "Panel Tests - Flame Graph", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-flamegraph/v0alpha1.panel_tests_flame_graph.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-flamegraph/v0alpha1.panel_tests_flame_graph.v42.v2beta1.json index 10b0b3ed930..982f4088303 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-flamegraph/v0alpha1.panel_tests_flame_graph.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-flamegraph/v0alpha1.panel_tests_flame_graph.v42.v2beta1.json @@ -134,5 +134,10 @@ "title": "Panel Tests - Flame Graph", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge-multi-series.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge-multi-series.v42.v2alpha1.json index f9046bea886..706d0bafd47 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge-multi-series.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge-multi-series.v42.v2alpha1.json @@ -499,5 +499,10 @@ "title": "Panel Tests - Gauge Multi Series", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge-multi-series.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge-multi-series.v42.v2beta1.json index da3a0f5e6f1..d0cbc9aa44e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge-multi-series.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge-multi-series.v42.v2beta1.json @@ -534,5 +534,10 @@ "title": "Panel Tests - Gauge Multi Series", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2alpha1.json index bd213346a6a..77874ee6291 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2alpha1.json @@ -1429,5 +1429,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2beta1.json index 4e6d53c09ab..663abfbf74c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2beta1.json @@ -1469,5 +1469,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v1beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v1beta1.json index e24f3826810..2eeb3040e6d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v1beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v1beta1.json @@ -81,9 +81,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": true, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -160,9 +160,9 @@ "effects": { "barGlow": false, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -239,9 +239,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -318,9 +318,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -397,9 +397,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -476,9 +476,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": false, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -555,9 +555,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": false, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -647,9 +647,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -726,9 +726,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -805,9 +805,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -884,9 +884,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -980,9 +980,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1059,9 +1059,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1138,9 +1138,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1217,9 +1217,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1296,9 +1296,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1392,9 +1392,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1475,9 +1475,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1558,9 +1558,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1650,9 +1650,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -1736,9 +1736,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -1836,9 +1836,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -1923,10 +1923,10 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, "sparkline": false, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -2010,9 +2010,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -2096,9 +2096,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -2160,4 +2160,4 @@ "storedVersion": "v0alpha1" } } -} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2alpha1.json index 893cb3a5d72..4aecf4e0d9c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2alpha1.json @@ -77,9 +77,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": true, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -170,9 +170,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -267,9 +267,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -365,10 +365,10 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, "sparkline": false, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -464,9 +464,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -561,9 +561,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -657,9 +657,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -749,9 +749,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -841,9 +841,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -933,9 +933,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -1025,9 +1025,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": false, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -1117,9 +1117,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": false, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -1205,9 +1205,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1297,9 +1297,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1389,9 +1389,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1481,9 +1481,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1577,9 +1577,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1665,9 +1665,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1757,9 +1757,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1853,9 +1853,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1949,9 +1949,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -2049,9 +2049,9 @@ "effects": { "barGlow": false, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -2141,9 +2141,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -2233,9 +2233,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -2326,9 +2326,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -2820,5 +2820,10 @@ "title": "Panel tests - Gauge (new)", "variables": [] }, - "status": {} -} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2beta1.json index ce8327e9434..6b567f19b5e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2beta1.json @@ -81,9 +81,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": true, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -177,9 +177,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -277,9 +277,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -378,10 +378,10 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, "sparkline": false, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -480,9 +480,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -580,9 +580,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -679,9 +679,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -774,9 +774,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -869,9 +869,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -964,9 +964,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -1059,9 +1059,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": false, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -1154,9 +1154,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": false, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -1245,9 +1245,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1340,9 +1340,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1435,9 +1435,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1530,9 +1530,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1629,9 +1629,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1720,9 +1720,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1815,9 +1815,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1914,9 +1914,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -2013,9 +2013,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -2116,9 +2116,9 @@ "effects": { "barGlow": false, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -2211,9 +2211,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -2306,9 +2306,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -2402,9 +2402,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -2896,5 +2896,10 @@ "title": "Panel tests - Gauge (new)", "variables": [] }, - "status": {} -} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v1beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v1beta1.json index 0487d6480e8..959f0193ad6 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v1beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v1beta1.json @@ -961,9 +961,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1175,4 +1175,4 @@ "storedVersion": "v0alpha1" } } -} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2alpha1.json index b3841bd8c19..66b29e88d13 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2alpha1.json @@ -864,9 +864,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1614,5 +1614,10 @@ "title": "Panel tests - Old gauge to new", "variables": [] }, - "status": {} -} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2beta1.json index fdcda577f2e..b870d0a91ad 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2beta1.json @@ -901,9 +901,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1666,5 +1666,10 @@ "title": "Panel tests - Old gauge to new", "variables": [] }, - "status": {} -} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-color-field.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-color-field.v42.v2alpha1.json index fe6b8e09647..8f6ae2065e2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-color-field.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-color-field.v42.v2alpha1.json @@ -671,5 +671,10 @@ "title": "Geomap - color field Copy", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-color-field.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-color-field.v42.v2beta1.json index ccd502e8e11..e567e6dbae2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-color-field.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-color-field.v42.v2beta1.json @@ -683,5 +683,10 @@ "title": "Geomap - color field Copy", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-photo-layer.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-photo-layer.v42.v2alpha1.json index f55700a79cd..135c8d98558 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-photo-layer.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-photo-layer.v42.v2alpha1.json @@ -201,5 +201,10 @@ "title": "Panel Tests - Geomap Photo Layer", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-photo-layer.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-photo-layer.v42.v2beta1.json index 4a7594b73a9..ce7d1c02a17 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-photo-layer.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-photo-layer.v42.v2beta1.json @@ -205,5 +205,10 @@ "title": "Panel Tests - Geomap Photo Layer", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-route-layer.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-route-layer.v42.v2alpha1.json index 92f520e0857..32cc829050d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-route-layer.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-route-layer.v42.v2alpha1.json @@ -341,5 +341,10 @@ "title": "Panel Tests - Geomap Fit to Data - Multiple Layer Types", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-route-layer.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-route-layer.v42.v2beta1.json index 82b86117485..758836fdd75 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-route-layer.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-route-layer.v42.v2beta1.json @@ -347,5 +347,10 @@ "title": "Panel Tests - Geomap Fit to Data - Multiple Layer Types", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-spatial-operations-transformer.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-spatial-operations-transformer.v42.v2alpha1.json index 87e64b32650..f6a93ab505e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-spatial-operations-transformer.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-spatial-operations-transformer.v42.v2alpha1.json @@ -173,5 +173,10 @@ "title": "Panel Tests - Geomap geohash transformer", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-spatial-operations-transformer.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-spatial-operations-transformer.v42.v2beta1.json index 5184ebb6680..376a2e0b52a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-spatial-operations-transformer.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-spatial-operations-transformer.v42.v2beta1.json @@ -177,5 +177,10 @@ "title": "Panel Tests - Geomap geohash transformer", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-v91.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-v91.v42.v2alpha1.json index 6ae35916b91..aecc89871f9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-v91.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-v91.v42.v2alpha1.json @@ -457,5 +457,10 @@ "title": "Panel Tests - Geomap 9.1", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-v91.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-v91.v42.v2beta1.json index a385b59567b..0bbab3d3711 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-v91.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap-v91.v42.v2beta1.json @@ -467,5 +467,10 @@ "title": "Panel Tests - Geomap 9.1", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap_multi-layers.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap_multi-layers.v42.v2alpha1.json index f7374506e86..d4c2a4a8145 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap_multi-layers.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap_multi-layers.v42.v2alpha1.json @@ -538,5 +538,10 @@ "title": "Panel Tests - Geomap Multi Layers", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap_multi-layers.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap_multi-layers.v42.v2beta1.json index b244899e6d4..4726ddb6e4b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap_multi-layers.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.geomap_multi-layers.v42.v2beta1.json @@ -545,5 +545,10 @@ "title": "Panel Tests - Geomap Multi Layers", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.panel-geomap.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.panel-geomap.v42.v2alpha1.json index 517c35c8e98..209afba2ff2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.panel-geomap.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.panel-geomap.v42.v2alpha1.json @@ -594,5 +594,10 @@ "title": "Panel Tests - Geomap", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.panel-geomap.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.panel-geomap.v42.v2beta1.json index 2d8549a8d89..4fdd338e7bd 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.panel-geomap.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-geomap/v0alpha1.panel-geomap.v42.v2beta1.json @@ -607,5 +607,10 @@ "title": "Panel Tests - Geomap", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2alpha1.json index 44ab8d372ff..12c84560a90 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2alpha1.json @@ -310,5 +310,10 @@ "title": "Panel Tests - Graph - Gradient Area Fills", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2beta1.json index 6beb26d4e70..9ae939f5d4f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2beta1.json @@ -320,5 +320,10 @@ "title": "Panel Tests - Graph - Gradient Area Fills", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2alpha1.json index 4628e8234f0..e0203a652e2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2alpha1.json @@ -971,5 +971,10 @@ "title": "Panel Tests - shared tooltips", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2beta1.json index 540a9010d55..fe835b1b44e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2beta1.json @@ -990,5 +990,10 @@ "title": "Panel Tests - shared tooltips", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2alpha1.json index 2dd4dd48907..e09a03ec44a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2alpha1.json @@ -351,5 +351,10 @@ "title": "Panel Tests - Graph Time Regions", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2beta1.json index 7a1aa009f08..f6eaa477161 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2beta1.json @@ -368,5 +368,10 @@ "title": "Panel Tests - Graph Time Regions", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json index e251ae6c6db..e67e659b5e8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json @@ -1752,5 +1752,10 @@ "title": "Panel Tests - Graph", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json index a87d37bc099..7a94d1842a5 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json @@ -1875,5 +1875,10 @@ "title": "Panel Tests - Graph", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2alpha1.json index 33b1bd246ee..25ef1324bc4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2alpha1.json @@ -623,5 +623,10 @@ "title": "Panel Tests - Graph - Y axis ticks", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2beta1.json index bf16659bc49..4534fe38da4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2beta1.json @@ -643,5 +643,10 @@ "title": "Panel Tests - Graph - Y axis ticks", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-calculate-log.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-calculate-log.v42.v2alpha1.json index a5567148d48..fb399db27f9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-calculate-log.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-calculate-log.v42.v2alpha1.json @@ -1113,5 +1113,10 @@ "title": "Heatmap calculate (log)", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-calculate-log.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-calculate-log.v42.v2beta1.json index 32816087c1d..1721f0b1c1f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-calculate-log.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-calculate-log.v42.v2beta1.json @@ -1133,5 +1133,10 @@ "title": "Heatmap calculate (log)", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2alpha1.json index 69764686c12..4873fe5fc74 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2alpha1.json @@ -414,5 +414,10 @@ "title": "Legacy heatmap", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2beta1.json index 7450f5af8ed..2e11457ec4d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2beta1.json @@ -432,5 +432,10 @@ "title": "Legacy heatmap", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2alpha1.json index fdcb368edc4..2eb67e36f2f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2alpha1.json @@ -542,5 +542,10 @@ "title": "Heatmap X axis", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2beta1.json index b6d51918cbc..acba4cedbc2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-x.v42.v2beta1.json @@ -554,5 +554,10 @@ "title": "Heatmap X axis", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2alpha1.json index cc15b1c4c91..57fbcad9d99 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2alpha1.json @@ -1243,5 +1243,10 @@ "title": "Panel Tests - Histogram", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2beta1.json index 81e56c3d318..5b2ee8d8df2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-histogram/v0alpha1.histogram_tests.v42.v2beta1.json @@ -1276,5 +1276,10 @@ "title": "Panel Tests - Histogram", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2alpha1.json index 2efffd9390c..2277a05c6bb 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2alpha1.json @@ -193,5 +193,10 @@ "title": "Panel - Panel Library", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2beta1.json index 20438d25434..2f61f5977d3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2beta1.json @@ -197,5 +197,10 @@ "title": "Panel - Panel Library", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-piechart/v0alpha1.panel_test_piechart.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-piechart/v0alpha1.panel_test_piechart.v42.v2alpha1.json index 742bed31cf6..84c03baac66 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-piechart/v0alpha1.panel_test_piechart.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-piechart/v0alpha1.panel_test_piechart.v42.v2alpha1.json @@ -770,5 +770,10 @@ "title": "Panel Tests - Pie chart", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-piechart/v0alpha1.panel_test_piechart.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-piechart/v0alpha1.panel_test_piechart.v42.v2beta1.json index b79118ec1bb..4e946c6aee9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-piechart/v0alpha1.panel_test_piechart.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-piechart/v0alpha1.panel_test_piechart.v42.v2beta1.json @@ -786,5 +786,10 @@ "title": "Panel Tests - Pie chart", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2alpha1.json index 97d355f227a..68c7c2b6529 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2alpha1.json @@ -424,5 +424,10 @@ "title": "Panel Tests - Polystat", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2beta1.json index 46644077d0c..f72f1e3b485 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2beta1.json @@ -457,5 +457,10 @@ "title": "Panel Tests - Polystat", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-stat/v0alpha1.panel-stat-tests.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-stat/v0alpha1.panel-stat-tests.v42.v2alpha1.json index acb35da6bb8..2074ff4fa12 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-stat/v0alpha1.panel-stat-tests.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-stat/v0alpha1.panel-stat-tests.v42.v2alpha1.json @@ -2768,5 +2768,10 @@ "title": "Panel Tests - Stat", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-stat/v0alpha1.panel-stat-tests.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-stat/v0alpha1.panel-stat-tests.v42.v2beta1.json index fb9f810cfaa..98b11ebadb3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-stat/v0alpha1.panel-stat-tests.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-stat/v0alpha1.panel-stat-tests.v42.v2beta1.json @@ -2883,5 +2883,10 @@ "title": "Panel Tests - Stat", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2alpha1.json index 44db8ddd292..d90c8dc52cd 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2alpha1.json @@ -1498,5 +1498,10 @@ "title": "StatusHistory - Thresholds \u0026 Mappings", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2beta1.json index 513bc4d476c..7aaa0fff33a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-status-history/v0alpha1.status-history-thresholds-mappings.v42.v2beta1.json @@ -1537,5 +1537,10 @@ "title": "StatusHistory - Thresholds \u0026 Mappings", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2alpha1.json index 2697100f4cd..540f0d9e54d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2alpha1.json @@ -1893,5 +1893,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2beta1.json index ec3f1d1ed5c..6c9aa023163 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_footer.v42.v2beta1.json @@ -1939,5 +1939,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2alpha1.json index d2ec399b434..bccce10d162 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2alpha1.json @@ -2192,5 +2192,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2beta1.json index a2ea184c554..5e186ef1443 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_kitchen_sink.v42.v2beta1.json @@ -2227,5 +2227,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_markdown.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_markdown.v42.v2alpha1.json index 2bbd6a111bc..4863b46eeb3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_markdown.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_markdown.v42.v2alpha1.json @@ -178,5 +178,10 @@ "title": "Panel Tests - Table - Markdown", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_markdown.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_markdown.v42.v2beta1.json index 4a1f3a61afd..1964dd78543 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_markdown.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_markdown.v42.v2beta1.json @@ -181,5 +181,10 @@ "title": "Panel Tests - Table - Markdown", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_pagination.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_pagination.v42.v2alpha1.json index e637af09f52..cb0c2bbc16d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_pagination.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_pagination.v42.v2alpha1.json @@ -3000,5 +3000,10 @@ "title": "Panel Tests - Table - Pagination", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_pagination.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_pagination.v42.v2beta1.json index bc15692c4cb..d79b6c9e68b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_pagination.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_pagination.v42.v2beta1.json @@ -3018,5 +3018,10 @@ "title": "Panel Tests - Table - Pagination", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2alpha1.json index 78012512792..e5e260fd150 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2alpha1.json @@ -824,5 +824,10 @@ "title": "Panel Tests - Table - Sparklines", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2beta1.json index 352bab0c09b..ac15a298939 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_sparkline_cell.v42.v2beta1.json @@ -846,5 +846,10 @@ "title": "Panel Tests - Table - Sparklines", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2alpha1.json index 9d3a3f84f61..2bcedabe196 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2alpha1.json @@ -426,5 +426,10 @@ "title": "Panel Tests - Table", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2beta1.json index 2b97a31452a..2d84b370019 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2beta1.json @@ -451,5 +451,10 @@ "title": "Panel Tests - Table", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests_new.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests_new.v42.v2alpha1.json index 25624e5c6d1..4e35b317cd9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests_new.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests_new.v42.v2alpha1.json @@ -1252,5 +1252,10 @@ "title": "Panel Tests - React Table", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests_new.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests_new.v42.v2beta1.json index b8d5f8acae3..8911415d0fc 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests_new.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests_new.v42.v2beta1.json @@ -1280,5 +1280,10 @@ "title": "Panel Tests - React Table", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2alpha1.json index b8e6725ca5a..1b6348c35d5 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2alpha1.json @@ -2507,5 +2507,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2beta1.json index 4a7ea3bcf33..a77140c5beb 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_v12_2_migrations.v42.v2beta1.json @@ -2550,5 +2550,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-text/v0alpha1.text-options.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-text/v0alpha1.text-options.v42.v2alpha1.json index e0299a0be0f..3ac4d6901f4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-text/v0alpha1.text-options.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-text/v0alpha1.text-options.v42.v2alpha1.json @@ -431,5 +431,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-text/v0alpha1.text-options.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-text/v0alpha1.text-options.v42.v2beta1.json index a93a6c13852..6e8243f3131 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-text/v0alpha1.text-options.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-text/v0alpha1.text-options.v42.v2beta1.json @@ -450,5 +450,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-endtime.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-endtime.v42.v2alpha1.json index 15877291ee7..cb30e79d196 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-endtime.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-endtime.v42.v2alpha1.json @@ -356,5 +356,10 @@ "title": "Panel Tests - StateTimeline - multiple frames with endTime", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-endtime.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-endtime.v42.v2beta1.json index 1d857be3ea5..34c3f244b54 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-endtime.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-endtime.v42.v2beta1.json @@ -365,5 +365,10 @@ "title": "Panel Tests - StateTimeline - multiple frames with endTime", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-nulls-retain.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-nulls-retain.v42.v2alpha1.json index 7a2c436af82..13303a094ec 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-nulls-retain.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-nulls-retain.v42.v2alpha1.json @@ -263,5 +263,10 @@ "title": "Panel Tests - StateTimeline - multiple frames with nulls", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-nulls-retain.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-nulls-retain.v42.v2beta1.json index 4b14dcf2810..7d8376e9106 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-nulls-retain.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-align-nulls-retain.v42.v2beta1.json @@ -267,5 +267,10 @@ "title": "Panel Tests - StateTimeline - multiple frames with nulls", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-demo.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-demo.v42.v2alpha1.json index 522ec817718..82500d25a30 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-demo.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-demo.v42.v2alpha1.json @@ -670,5 +670,10 @@ "title": "Timeline Demo", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-demo.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-demo.v42.v2beta1.json index 8e01aece508..89c7db392ea 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-demo.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-demo.v42.v2beta1.json @@ -699,5 +699,10 @@ "title": "Timeline Demo", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-modes.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-modes.v42.v2alpha1.json index 7c8170cbcf8..9b4b2edfb80 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-modes.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-modes.v42.v2alpha1.json @@ -548,5 +548,10 @@ "title": "Timeline Modes", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-modes.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-modes.v42.v2beta1.json index f5f4d53dba6..35d2643cc81 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-modes.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-modes.v42.v2beta1.json @@ -566,5 +566,10 @@ "title": "Timeline Modes", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2alpha1.json index ff492690353..8dff3c34ccf 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2alpha1.json @@ -1498,5 +1498,10 @@ "title": "StateTimeline - Thresholds \u0026 Mappings", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2beta1.json index 6dcd28290db..3baa3d21130 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeline/v0alpha1.timeline-thresholds-mappings.v42.v2beta1.json @@ -1537,5 +1537,10 @@ "title": "StateTimeline - Thresholds \u0026 Mappings", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-bars-high-density.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-bars-high-density.v42.v2alpha1.json index f18b9741878..6f3390c4d11 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-bars-high-density.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-bars-high-density.v42.v2alpha1.json @@ -790,5 +790,10 @@ "title": "Panel Tests - TimeSeries - bars high density (stroke + fill)", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-bars-high-density.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-bars-high-density.v42.v2beta1.json index 797c81e39f0..00de0e2092b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-bars-high-density.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-bars-high-density.v42.v2beta1.json @@ -805,5 +805,10 @@ "title": "Panel Tests - TimeSeries - bars high density (stroke + fill)", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-by-value-color-schemes.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-by-value-color-schemes.v42.v2alpha1.json index d51c3443680..2d0e6b76950 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-by-value-color-schemes.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-by-value-color-schemes.v42.v2alpha1.json @@ -1374,5 +1374,10 @@ "title": "Panel Tests - Graph NG - By value color schemes", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-by-value-color-schemes.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-by-value-color-schemes.v42.v2beta1.json index cc2f62859f9..a3f729f9c5f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-by-value-color-schemes.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-by-value-color-schemes.v42.v2beta1.json @@ -1403,5 +1403,10 @@ "title": "Panel Tests - Graph NG - By value color schemes", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-formats.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-formats.v42.v2alpha1.json index 766832b0cb7..ac083165532 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-formats.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-formats.v42.v2alpha1.json @@ -1067,5 +1067,10 @@ "title": "Panel Tests - Timeseries - Supported input formats", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-formats.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-formats.v42.v2beta1.json index da1784c2b52..25ed3a1cf9b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-formats.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-formats.v42.v2beta1.json @@ -1076,5 +1076,10 @@ "title": "Panel Tests - Timeseries - Supported input formats", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2alpha1.json index 7ad3c386000..5b43876c65f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2alpha1.json @@ -799,5 +799,10 @@ "title": "Panel Tests - Graph NG - Gradient Area Fills", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2beta1.json index 4def562428b..ca96f9d5720 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-gradient-area.v42.v2beta1.json @@ -813,5 +813,10 @@ "title": "Panel Tests - Graph NG - Gradient Area Fills", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2alpha1.json index 0f5a4c7473e..74cba148009 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2alpha1.json @@ -1215,5 +1215,10 @@ "title": "Panel Tests - GraphNG - Hue Gradients", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2beta1.json index e7e54fd938b..7e64bc79ef3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-hue-gradients.v42.v2beta1.json @@ -1235,5 +1235,10 @@ "title": "Panel Tests - GraphNG - Hue Gradients", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-nulls.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-nulls.v42.v2alpha1.json index 9bf92a90234..718a96c265c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-nulls.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-nulls.v42.v2alpha1.json @@ -4362,5 +4362,10 @@ "title": "Panel Tests - Graph NG - Discrete panels", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-nulls.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-nulls.v42.v2beta1.json index 3678e2c1bb8..187a377bdf1 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-nulls.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-nulls.v42.v2beta1.json @@ -4462,5 +4462,10 @@ "title": "Panel Tests - Graph NG - Discrete panels", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-out-of-rage.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-out-of-rage.v42.v2alpha1.json index 7bfea9b01ad..f1bff310950 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-out-of-rage.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-out-of-rage.v42.v2alpha1.json @@ -660,5 +660,10 @@ "title": "Panel Tests - Timeseries - Out of range", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-out-of-rage.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-out-of-rage.v42.v2beta1.json index b0ee8fe1273..e868bafcffd 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-out-of-rage.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-out-of-rage.v42.v2beta1.json @@ -676,5 +676,10 @@ "title": "Panel Tests - Timeseries - Out of range", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-shared-tooltip-cursor-position.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-shared-tooltip-cursor-position.v42.v2alpha1.json index 19003b3c112..dbe2ae22ebd 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-shared-tooltip-cursor-position.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-shared-tooltip-cursor-position.v42.v2alpha1.json @@ -821,5 +821,10 @@ "title": "Panel Tests - shared tooltips cursor positioning", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-shared-tooltip-cursor-position.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-shared-tooltip-cursor-position.v42.v2beta1.json index 17006d8b0ac..5763494659d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-shared-tooltip-cursor-position.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-shared-tooltip-cursor-position.v42.v2beta1.json @@ -839,5 +839,10 @@ "title": "Panel Tests - shared tooltips cursor positioning", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-soft-limits.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-soft-limits.v42.v2alpha1.json index 91ebfa89a29..3c8700d1c0a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-soft-limits.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-soft-limits.v42.v2alpha1.json @@ -2849,5 +2849,10 @@ "title": "Panel Tests - Graph NG - softMin/softMax", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-soft-limits.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-soft-limits.v42.v2beta1.json index b616f35f02f..5cd6604807a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-soft-limits.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-soft-limits.v42.v2beta1.json @@ -2887,5 +2887,10 @@ "title": "Panel Tests - Graph NG - softMin/softMax", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking.v42.v2alpha1.json index d5de67f8b7e..59b356d1a36 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking.v42.v2alpha1.json @@ -1521,5 +1521,10 @@ "title": "Panel Tests - TimeSeries - stacking", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking.v42.v2beta1.json index b14bc2dfae4..07c5f127c32 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking.v42.v2beta1.json @@ -1566,5 +1566,10 @@ "title": "Panel Tests - TimeSeries - stacking", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking2.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking2.v42.v2alpha1.json index c37056e01ec..4c92eef28c1 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking2.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking2.v42.v2alpha1.json @@ -4629,5 +4629,10 @@ "title": "TimeSeries \u0026 BarChart Stacking", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking2.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking2.v42.v2beta1.json index 200a8362e4e..486f41cdce0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking2.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-stacking2.v42.v2beta1.json @@ -4818,5 +4818,10 @@ "title": "TimeSeries \u0026 BarChart Stacking", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2alpha1.json index 99bf0a4bba4..c9a9c26558e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2alpha1.json @@ -1185,5 +1185,10 @@ "title": "Panel Tests - GraphNG Thresholds", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2beta1.json index d1f32f0900d..55ed3cf155c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2beta1.json @@ -1211,5 +1211,10 @@ "title": "Panel Tests - GraphNG Thresholds", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-time.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-time.v42.v2alpha1.json index cf0c37a3a79..43271c576dd 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-time.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-time.v42.v2alpha1.json @@ -735,5 +735,10 @@ "title": "Panel Tests - GraphNG - Time Axis", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-time.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-time.v42.v2beta1.json index f106b3c8e1a..3674472eb53 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-time.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-time.v42.v2beta1.json @@ -749,5 +749,10 @@ "title": "Panel Tests - GraphNG - Time Axis", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-y-ticks-zero-decimals.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-y-ticks-zero-decimals.v42.v2alpha1.json index 4a6a5b03287..27afd1ec327 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-y-ticks-zero-decimals.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-y-ticks-zero-decimals.v42.v2alpha1.json @@ -1095,5 +1095,10 @@ "title": "Zero Decimals Y Ticks", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-y-ticks-zero-decimals.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-y-ticks-zero-decimals.v42.v2beta1.json index 0d41fc01b8f..ef2241fb8f3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-y-ticks-zero-decimals.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-y-ticks-zero-decimals.v42.v2beta1.json @@ -1121,5 +1121,10 @@ "title": "Zero Decimals Y Ticks", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-yaxis-ticks.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-yaxis-ticks.v42.v2alpha1.json index d23c438178c..40bf202a1ae 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-yaxis-ticks.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-yaxis-ticks.v42.v2alpha1.json @@ -1722,5 +1722,10 @@ "title": "Panel Tests - Graph NG - Y axis ticks", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-yaxis-ticks.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-yaxis-ticks.v42.v2beta1.json index 923573205a8..6e3377b4cc4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-yaxis-ticks.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-yaxis-ticks.v42.v2beta1.json @@ -1765,5 +1765,10 @@ "title": "Panel Tests - Graph NG - Y axis ticks", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2alpha1.json index cb50db62a68..e50b453076a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2alpha1.json @@ -4371,5 +4371,10 @@ "title": "Panel Tests - Graph NG", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2beta1.json index 249af246ac9..65105663c85 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries.v42.v2beta1.json @@ -4445,5 +4445,10 @@ "title": "Panel Tests - Graph NG", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-trend/v0alpha1.trend_example.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-trend/v0alpha1.trend_example.v42.v2alpha1.json index dda9209aa94..1439756b694 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-trend/v0alpha1.trend_example.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-trend/v0alpha1.trend_example.v42.v2alpha1.json @@ -270,5 +270,10 @@ "title": "Panel Tests - Trend", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-trend/v0alpha1.trend_example.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-trend/v0alpha1.trend_example.v42.v2beta1.json index 11683b31b98..65de2a79cf4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-trend/v0alpha1.trend_example.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-trend/v0alpha1.trend_example.v42.v2beta1.json @@ -274,5 +274,10 @@ "title": "Panel Tests - Trend", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-demo.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-demo.v42.v2alpha1.json index 53122268d1d..76278b42b57 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-demo.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-demo.v42.v2alpha1.json @@ -1785,5 +1785,10 @@ "title": "Panel Tests - XY Chart Demo", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-demo.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-demo.v42.v2beta1.json index a4c1b86c07e..70a7b3b7537 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-demo.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-demo.v42.v2beta1.json @@ -1825,5 +1825,10 @@ "title": "Panel Tests - XY Chart Demo", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-migrations.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-migrations.v42.v2alpha1.json index 33d0b2ee9b2..ccb0c353c72 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-migrations.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-migrations.v42.v2alpha1.json @@ -2478,5 +2478,10 @@ "title": "Panel Tests - XY Chart migrations", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-migrations.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-migrations.v42.v2beta1.json index a43b5c5e1e9..add31528134 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-migrations.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-migrations.v42.v2beta1.json @@ -2515,5 +2515,10 @@ "title": "Panel Tests - XY Chart migrations", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2alpha1.json index 7583af8160e..1d60f0ef9bf 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2alpha1.json @@ -885,5 +885,10 @@ "title": "XYChart tooltip color test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2beta1.json index e10e9fc65ff..5a46646474d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-xychart/v0alpha1.xychart-tooltip-color-test.v42.v2beta1.json @@ -907,5 +907,10 @@ "title": "XYChart tooltip color test", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.mostly-blank-dashboard.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.mostly-blank-dashboard.v42.v2alpha1.json index 38e58c03f3a..d203fb8e357 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.mostly-blank-dashboard.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.mostly-blank-dashboard.v42.v2alpha1.json @@ -150,5 +150,10 @@ "title": "Mostly blank dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.mostly-blank-dashboard.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.mostly-blank-dashboard.v42.v2beta1.json index 45c5b980f7f..8f1a85fee5f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.mostly-blank-dashboard.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.mostly-blank-dashboard.v42.v2beta1.json @@ -153,5 +153,10 @@ "title": "Mostly blank dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.relative_time_zone_support.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.relative_time_zone_support.v42.v2alpha1.json index a8c0c9f5617..7e915f70e20 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.relative_time_zone_support.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.relative_time_zone_support.v42.v2alpha1.json @@ -250,5 +250,10 @@ "title": "Panel Tests - Relative time zone support", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.relative_time_zone_support.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.relative_time_zone_support.v42.v2beta1.json index e8b38167a2a..545a6a234b4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.relative_time_zone_support.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.relative_time_zone_support.v42.v2beta1.json @@ -257,5 +257,10 @@ "title": "Panel Tests - Relative time zone support", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2alpha1.json index a738ad9daba..c8314d47e25 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2alpha1.json @@ -851,5 +851,10 @@ "title": "Panel tests - Slow Queries \u0026 Annotations", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2beta1.json index 2d018bf0db4..90ce6da65a3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2beta1.json @@ -894,5 +894,10 @@ "title": "Panel tests - Slow Queries \u0026 Annotations", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.tall_dashboard.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.tall_dashboard.v42.v2alpha1.json index 027ae6f3352..5fd3f355cbb 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.tall_dashboard.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.tall_dashboard.v42.v2alpha1.json @@ -3416,5 +3416,10 @@ "title": "A tall dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.tall_dashboard.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.tall_dashboard.v42.v2beta1.json index a225f2e7d81..b0d10fd280b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.tall_dashboard.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.tall_dashboard.v42.v2beta1.json @@ -3517,5 +3517,10 @@ "title": "A tall dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2alpha1.json index 0d6468c31f1..c3413246704 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2alpha1.json @@ -532,5 +532,10 @@ "title": "Panel Tests - Time zone support", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2beta1.json index 348f2876085..b2146b3b82a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2beta1.json @@ -566,5 +566,10 @@ "title": "Panel Tests - Time zone support", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.config-from-query.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.config-from-query.v42.v2alpha1.json index 8808e6d73dc..a0e87e7d5f2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.config-from-query.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.config-from-query.v42.v2alpha1.json @@ -1014,5 +1014,10 @@ "title": "Transforms - Config from query", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.config-from-query.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.config-from-query.v42.v2beta1.json index 69930ae2e77..8134c573a69 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.config-from-query.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.config-from-query.v42.v2beta1.json @@ -1040,5 +1040,10 @@ "title": "Transforms - Config from query", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.extract-json-paths.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.extract-json-paths.v42.v2alpha1.json index 469fca5d2aa..a611e5b0db2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.extract-json-paths.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.extract-json-paths.v42.v2alpha1.json @@ -418,5 +418,10 @@ "title": "Transforms - Test extractFields JSON", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.extract-json-paths.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.extract-json-paths.v42.v2beta1.json index 396cbc7348b..e1229efccac 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.extract-json-paths.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.extract-json-paths.v42.v2beta1.json @@ -428,5 +428,10 @@ "title": "Transforms - Test extractFields JSON", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2alpha1.json index 9202330a572..056fdc62383 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2alpha1.json @@ -227,5 +227,10 @@ "title": "Transforms - Filters", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2beta1.json index c9d1b8f11c4..57c5559add1 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.filter.v42.v2beta1.json @@ -233,5 +233,10 @@ "title": "Transforms - Filters", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-field.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-field.v42.v2alpha1.json index 9797be322d6..cbd2e994f15 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-field.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-field.v42.v2alpha1.json @@ -865,5 +865,10 @@ "title": "Transforms - Join by field", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-field.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-field.v42.v2beta1.json index ab0fb924f23..8fc053627fd 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-field.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-field.v42.v2beta1.json @@ -884,5 +884,10 @@ "title": "Transforms - Join by field", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-labels.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-labels.v42.v2alpha1.json index 99353757d46..a10f2f6ed00 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-labels.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-labels.v42.v2alpha1.json @@ -510,5 +510,10 @@ "title": "Transforms - Join by labels", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-labels.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-labels.v42.v2beta1.json index fdc1b53ff36..73b06c4be7e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-labels.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.join-by-labels.v42.v2beta1.json @@ -526,5 +526,10 @@ "title": "Transforms - Join by labels", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.regression-analysis.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.regression-analysis.v42.v2alpha1.json index 22f053d066c..cab0fbea1e7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.regression-analysis.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.regression-analysis.v42.v2alpha1.json @@ -1348,5 +1348,10 @@ "title": "Transforms - Regression analysis", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.regression-analysis.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.regression-analysis.v42.v2beta1.json index 0d00e3f1021..464769f0e9e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.regression-analysis.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.regression-analysis.v42.v2beta1.json @@ -1371,5 +1371,10 @@ "title": "Transforms - Regression analysis", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.reuse.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.reuse.v42.v2alpha1.json index fdea0031183..08b35165f79 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.reuse.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.reuse.v42.v2alpha1.json @@ -741,5 +741,10 @@ "title": "Transforms - Reuse dashboard queries", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.reuse.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.reuse.v42.v2beta1.json index 79002c2d12c..d8db8e1ada9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.reuse.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.reuse.v42.v2beta1.json @@ -756,5 +756,10 @@ "title": "Transforms - Reuse dashboard queries", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.rows-to-fields.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.rows-to-fields.v42.v2alpha1.json index 4d47c35593c..f89d509cae8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.rows-to-fields.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.rows-to-fields.v42.v2alpha1.json @@ -963,5 +963,10 @@ "title": "Transforms - Rows to fields", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.rows-to-fields.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.rows-to-fields.v42.v2beta1.json index ddeb72b78f0..234f5dcbd63 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.rows-to-fields.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/transforms/v0alpha1.rows-to-fields.v42.v2beta1.json @@ -983,5 +983,10 @@ "title": "Transforms - Rows to fields", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2alpha1.json index b9aea52ad62..315164a75a3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2alpha1.json @@ -1578,5 +1578,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2beta1.json index 0bd3a37b2d8..94f898bfa16 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2beta1.json @@ -1624,5 +1624,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2alpha1.json index d50e38918d2..2a6500f86db 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2alpha1.json @@ -379,5 +379,10 @@ "title": "Grafana Dev Overview \u0026 Home", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2beta1.json index dc06779b135..0580a517cb2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2beta1.json @@ -386,5 +386,10 @@ "title": "Grafana Dev Overview \u0026 Home", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v2alpha1.json index 387ba145494..5f94f0d555b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v2alpha1.json @@ -335,5 +335,10 @@ "title": "Annotation Conversions Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v2beta1.json index a4dde4d26ac..c728d8dd513 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.annotation-conversions.v2beta1.json @@ -346,5 +346,10 @@ "title": "Annotation Conversions Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v2alpha1.json index 3e9132d9ea9..4de46378b53 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v2alpha1.json @@ -200,5 +200,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v2beta1.json index 951eeaac0f6..7576414d605 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.dashboard-properties.v2beta1.json @@ -205,5 +205,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.library-panel-repeat-options.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.library-panel-repeat-options.v2alpha1.json index a614f32dda6..a9b876424be 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.library-panel-repeat-options.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.library-panel-repeat-options.v2alpha1.json @@ -165,5 +165,10 @@ "title": "Library Panel Repeat Options Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.library-panel-repeat-options.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.library-panel-repeat-options.v2beta1.json index d06d12848e6..8b2eb35cef1 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.library-panel-repeat-options.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.library-panel-repeat-options.v2beta1.json @@ -165,5 +165,10 @@ "title": "Library Panel Repeat Options Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2alpha1.json index 9e0d5378f9b..ae10c4f3807 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2alpha1.json @@ -501,5 +501,10 @@ "title": "Panel Conversions Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2beta1.json index 8eac23d96c0..910dbbe3e1c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2beta1.json @@ -510,5 +510,10 @@ "title": "Panel Conversions Test Dashboard", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2alpha1.json index 5a0a8e074e6..55b0af155ff 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2alpha1.json @@ -201,5 +201,10 @@ "title": "Panel ds inheritance ", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2beta1.json index 3a13853c08e..48ec1ca758b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-datasource-type-datasource.v2beta1.json @@ -204,5 +204,10 @@ "title": "Panel ds inheritance ", "variables": [] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v2alpha1.json index c51582691b9..040513a3ce5 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v2alpha1.json @@ -414,5 +414,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v2beta1.json index 441258438a9..cf3ff5443c0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.variable-conversions.v2beta1.json @@ -417,5 +417,10 @@ } ] }, - "status": {} + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/v0.go b/apps/dashboard/pkg/migration/conversion/v0.go index 826293d9564..513f6975516 100644 --- a/apps/dashboard/pkg/migration/conversion/v0.go +++ b/apps/dashboard/pkg/migration/conversion/v0.go @@ -13,6 +13,9 @@ import ( func Convert_V0_to_V1beta1(in *dashv0.Dashboard, out *dashv1.Dashboard, scope conversion.Scope) error { if err := ConvertDashboard_V0_to_V1beta1(in, out, scope); err != nil { + out.ObjectMeta = in.ObjectMeta + out.APIVersion = dashv1.APIVERSION + out.Kind = in.Kind out.Status = dashv1.DashboardStatus{ Conversion: &dashv1.DashboardConversionStatus{ StoredVersion: ptr.To(dashv0.VERSION), @@ -22,12 +25,24 @@ func Convert_V0_to_V1beta1(in *dashv0.Dashboard, out *dashv1.Dashboard, scope co } return err } + + // Set successful conversion status + out.Status = dashv1.DashboardStatus{ + Conversion: &dashv1.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv0.VERSION), + Failed: false, + }, + } + return nil } func Convert_V0_to_V2alpha1(in *dashv0.Dashboard, out *dashv2alpha1.Dashboard, scope conversion.Scope, dsIndexProvider schemaversion.DataSourceIndexProvider, leIndexProvider schemaversion.LibraryElementIndexProvider) error { v1beta1 := &dashv1.Dashboard{} if err := ConvertDashboard_V0_to_V1beta1(in, v1beta1, scope); err != nil { + out.ObjectMeta = in.ObjectMeta + out.APIVersion = dashv2alpha1.APIVERSION + out.Kind = in.Kind out.Status = dashv2alpha1.DashboardStatus{ Conversion: &dashv2alpha1.DashboardConversionStatus{ StoredVersion: ptr.To(dashv0.VERSION), @@ -35,7 +50,6 @@ func Convert_V0_to_V2alpha1(in *dashv0.Dashboard, out *dashv2alpha1.Dashboard, s Error: ptr.To(err.Error()), }, } - // Don't return error - just set status (matches test expectations) // Ensure layout is set even on error to prevent JSON marshaling issues if out.Spec.Layout.GridLayoutKind == nil && out.Spec.Layout.RowsLayoutKind == nil { out.Spec.Layout = dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ @@ -45,10 +59,13 @@ func Convert_V0_to_V2alpha1(in *dashv0.Dashboard, out *dashv2alpha1.Dashboard, s }, } } - return nil + return err } if err := ConvertDashboard_V1beta1_to_V2alpha1(v1beta1, out, scope, dsIndexProvider, leIndexProvider); err != nil { + out.ObjectMeta = in.ObjectMeta + out.APIVersion = dashv2alpha1.APIVERSION + out.Kind = in.Kind out.Status = dashv2alpha1.DashboardStatus{ Conversion: &dashv2alpha1.DashboardConversionStatus{ StoredVersion: ptr.To(dashv0.VERSION), @@ -56,7 +73,6 @@ func Convert_V0_to_V2alpha1(in *dashv0.Dashboard, out *dashv2alpha1.Dashboard, s Error: ptr.To(err.Error()), }, } - // Don't return error - just set status (matches test expectations) // Ensure layout is set even on error to prevent JSON marshaling issues if out.Spec.Layout.GridLayoutKind == nil && out.Spec.Layout.RowsLayoutKind == nil { out.Spec.Layout = dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ @@ -66,7 +82,15 @@ func Convert_V0_to_V2alpha1(in *dashv0.Dashboard, out *dashv2alpha1.Dashboard, s }, } } - return nil + return err + } + + // Set successful conversion status + out.Status = dashv2alpha1.DashboardStatus{ + Conversion: &dashv2alpha1.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv0.VERSION), + Failed: false, + }, } return nil @@ -75,6 +99,9 @@ func Convert_V0_to_V2alpha1(in *dashv0.Dashboard, out *dashv2alpha1.Dashboard, s func Convert_V0_to_V2beta1(in *dashv0.Dashboard, out *dashv2beta1.Dashboard, scope conversion.Scope, dsIndexProvider schemaversion.DataSourceIndexProvider, leIndexProvider schemaversion.LibraryElementIndexProvider) error { v1beta1 := &dashv1.Dashboard{} if err := ConvertDashboard_V0_to_V1beta1(in, v1beta1, scope); err != nil { + out.ObjectMeta = in.ObjectMeta + out.APIVersion = dashv2beta1.APIVERSION + out.Kind = in.Kind out.Status = dashv2beta1.DashboardStatus{ Conversion: &dashv2beta1.DashboardConversionStatus{ StoredVersion: ptr.To(dashv0.VERSION), @@ -82,11 +109,23 @@ func Convert_V0_to_V2beta1(in *dashv0.Dashboard, out *dashv2beta1.Dashboard, sco Error: ptr.To(err.Error()), }, } + // Ensure layout is set even on error to prevent JSON marshaling issues + if out.Spec.Layout.GridLayoutKind == nil && out.Spec.Layout.RowsLayoutKind == nil { + out.Spec.Layout = dashv2beta1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2beta1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2beta1.DashboardGridLayoutSpec{}, + }, + } + } return err } v2alpha1 := &dashv2alpha1.Dashboard{} if err := ConvertDashboard_V1beta1_to_V2alpha1(v1beta1, v2alpha1, scope, dsIndexProvider, leIndexProvider); err != nil { + out.ObjectMeta = in.ObjectMeta + out.APIVersion = dashv2beta1.APIVERSION + out.Kind = in.Kind out.Status = dashv2beta1.DashboardStatus{ Conversion: &dashv2beta1.DashboardConversionStatus{ StoredVersion: ptr.To(dashv0.VERSION), @@ -94,10 +133,22 @@ func Convert_V0_to_V2beta1(in *dashv0.Dashboard, out *dashv2beta1.Dashboard, sco Error: ptr.To(err.Error()), }, } + // Ensure layout is set even on error to prevent JSON marshaling issues + if out.Spec.Layout.GridLayoutKind == nil && out.Spec.Layout.RowsLayoutKind == nil { + out.Spec.Layout = dashv2beta1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2beta1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2beta1.DashboardGridLayoutSpec{}, + }, + } + } return err } if err := ConvertDashboard_V2alpha1_to_V2beta1(v2alpha1, out, scope); err != nil { + out.ObjectMeta = in.ObjectMeta + out.APIVersion = dashv2beta1.APIVERSION + out.Kind = in.Kind out.Status = dashv2beta1.DashboardStatus{ Conversion: &dashv2beta1.DashboardConversionStatus{ StoredVersion: ptr.To(dashv0.VERSION), @@ -105,8 +156,25 @@ func Convert_V0_to_V2beta1(in *dashv0.Dashboard, out *dashv2beta1.Dashboard, sco Error: ptr.To(err.Error()), }, } + // Ensure layout is set even on error to prevent JSON marshaling issues + if out.Spec.Layout.GridLayoutKind == nil && out.Spec.Layout.RowsLayoutKind == nil { + out.Spec.Layout = dashv2beta1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2beta1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2beta1.DashboardGridLayoutSpec{}, + }, + } + } return err } + // Set successful conversion status + out.Status = dashv2beta1.DashboardStatus{ + Conversion: &dashv2beta1.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv0.VERSION), + Failed: false, + }, + } + return nil } diff --git a/apps/dashboard/pkg/migration/conversion/v0_test.go b/apps/dashboard/pkg/migration/conversion/v0_test.go index 5566a72c767..3620ea4d099 100644 --- a/apps/dashboard/pkg/migration/conversion/v0_test.go +++ b/apps/dashboard/pkg/migration/conversion/v0_test.go @@ -54,7 +54,7 @@ func TestV0ConversionErrorHandling(t *testing.T) { }, }, { - name: "Convert_V0_to_V2alpha1 sets status on first step migration failure", + name: "Convert_V0_to_V2alpha1 returns error and sets status on first step migration failure", source: &dashv0.Dashboard{ ObjectMeta: metav1.ObjectMeta{ Namespace: "org-1", @@ -67,7 +67,7 @@ func TestV0ConversionErrorHandling(t *testing.T) { }, }, target: &dashv2alpha1.Dashboard{}, - expectError: false, // Convert_V0_to_V2alpha1 doesn't return error, just sets status + expectError: true, // Convert_V0_to_V2alpha1 now returns error for proper metrics/logging expectStatusSet: true, checkStatus: func(t *testing.T, target interface{}) { out := target.(*dashv2alpha1.Dashboard) @@ -277,7 +277,7 @@ func TestV0ConversionSecondStepErrors(t *testing.T) { leProvider := migrationtestutil.NewLibraryElementProvider() migration.Initialize(dsProvider, leProvider, migration.DefaultCacheTTL) - t.Run("Convert_V0_to_V2alpha1 sets status on first step error", func(t *testing.T) { + t.Run("Convert_V0_to_V2alpha1 returns error and sets status on first step error", func(t *testing.T) { // Create a dashboard that will fail v0->v1beta1 conversion // Use schemaVersion 0 which will cause migration to fail source := &dashv0.Dashboard{ @@ -295,16 +295,16 @@ func TestV0ConversionSecondStepErrors(t *testing.T) { err := Convert_V0_to_V2alpha1(source, target, nil, dsProvider, leProvider) - // Convert_V0_to_V2alpha1 doesn't return error, just sets status - require.NoError(t, err, "Convert_V0_to_V2alpha1 doesn't return error") - // Status should be set when first step fails + // Convert_V0_to_V2alpha1 returns error for proper metrics/logging + require.Error(t, err, "Convert_V0_to_V2alpha1 should return error") + // Status should also be set when first step fails require.NotNil(t, target.Status.Conversion, "Status should be set on first step error") require.True(t, target.Status.Conversion.Failed, "Failed should be true") require.NotNil(t, target.Status.Conversion.Error, "Error should be set") require.Equal(t, dashv0.VERSION, *target.Status.Conversion.StoredVersion) }) - t.Run("Convert_V0_to_V2alpha1 sets status on second step error", func(t *testing.T) { + t.Run("Convert_V0_to_V2alpha1 returns error and sets status on second step error", func(t *testing.T) { // Create a dashboard that will pass v0->v1beta1 but fail v1beta1->v2alpha1 // We need to create invalid JSON structure that will cause JSON marshaling to fail // or create a dashboard with invalid structure that causes transformation to fail @@ -329,16 +329,13 @@ func TestV0ConversionSecondStepErrors(t *testing.T) { err := Convert_V0_to_V2alpha1(source, target, nil, dsProvider, leProvider) - // Convert_V0_to_V2alpha1 doesn't return error, just sets status - require.NoError(t, err, "Convert_V0_to_V2alpha1 doesn't return error") - // If second step fails, status should be set - // Note: The error might occur in first step if JSON marshal fails early, - // but we're testing that the error handling path exists - if target.Status.Conversion != nil && target.Status.Conversion.Failed { - require.True(t, target.Status.Conversion.Failed) - require.NotNil(t, target.Status.Conversion.Error) - require.Equal(t, dashv0.VERSION, *target.Status.Conversion.StoredVersion) - } + // Convert_V0_to_V2alpha1 returns error for proper metrics/logging + require.Error(t, err, "Convert_V0_to_V2alpha1 should return error") + // Status should also be set + require.NotNil(t, target.Status.Conversion, "Status should be set on error") + require.True(t, target.Status.Conversion.Failed, "Failed should be true") + require.NotNil(t, target.Status.Conversion.Error, "Error should be set") + require.Equal(t, dashv0.VERSION, *target.Status.Conversion.StoredVersion) }) t.Run("Convert_V0_to_V2beta1 returns error on second step failure", func(t *testing.T) { @@ -394,3 +391,177 @@ func TestV0ConversionSecondStepErrors(t *testing.T) { } }) } + +// TestV0ConversionConsistency_ErrorsMustBeReturned ensures v0 conversion functions +// return errors instead of swallowing them. This prevents metrics and logs from being silently dropped. +func TestV0ConversionConsistency_ErrorsMustBeReturned(t *testing.T) { + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + leProvider := migrationtestutil.NewLibraryElementProvider() + migration.Initialize(dsProvider, leProvider, migration.DefaultCacheTTL) + + // Create a v0 dashboard that will fail conversion (invalid schema version) + invalidV0 := &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + Name: "test-dashboard", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 0, // Invalid schema version causes migration to fail + }, + }, + } + + t.Run("Convert_V0_to_V1beta1 must return error on failure", func(t *testing.T) { + target := &dashv1.Dashboard{} + err := Convert_V0_to_V1beta1(invalidV0, target, nil) + require.Error(t, err, "Convert_V0_to_V1beta1 must return error, not swallow it") + require.True(t, target.Status.Conversion.Failed, "Status.Conversion.Failed must be true") + }) + + t.Run("Convert_V0_to_V2alpha1 must return error on failure", func(t *testing.T) { + target := &dashv2alpha1.Dashboard{} + err := Convert_V0_to_V2alpha1(invalidV0, target, nil, dsProvider, leProvider) + require.Error(t, err, "Convert_V0_to_V2alpha1 must return error, not swallow it") + require.True(t, target.Status.Conversion.Failed, "Status.Conversion.Failed must be true") + }) + + t.Run("Convert_V0_to_V2beta1 must return error on failure", func(t *testing.T) { + target := &dashv2beta1.Dashboard{} + err := Convert_V0_to_V2beta1(invalidV0, target, nil, dsProvider, leProvider) + require.Error(t, err, "Convert_V0_to_V2beta1 must return error, not swallow it") + require.True(t, target.Status.Conversion.Failed, "Status.Conversion.Failed must be true") + }) +} + +// TestV0ConversionConsistency_SuccessStatusMustBeSet ensures v0 conversion functions +// set Status.Conversion with Failed=false on successful conversion. +func TestV0ConversionConsistency_SuccessStatusMustBeSet(t *testing.T) { + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + leProvider := migrationtestutil.NewLibraryElementProvider() + migration.Initialize(dsProvider, leProvider, migration.DefaultCacheTTL) + + // Create a valid v0 dashboard + validV0 := &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test-dashboard", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 42, + }, + }, + } + + t.Run("Convert_V0_to_V1beta1 must set success status", func(t *testing.T) { + target := &dashv1.Dashboard{} + err := Convert_V0_to_V1beta1(validV0, target, nil) + require.NoError(t, err) + require.NotNil(t, target.Status.Conversion, "Status.Conversion must be set on success") + require.False(t, target.Status.Conversion.Failed, "Status.Conversion.Failed must be false on success") + require.NotNil(t, target.Status.Conversion.StoredVersion, "StoredVersion must be set") + require.Equal(t, dashv0.VERSION, *target.Status.Conversion.StoredVersion) + }) + + t.Run("Convert_V0_to_V2alpha1 must set success status", func(t *testing.T) { + target := &dashv2alpha1.Dashboard{} + err := Convert_V0_to_V2alpha1(validV0, target, nil, dsProvider, leProvider) + require.NoError(t, err) + require.NotNil(t, target.Status.Conversion, "Status.Conversion must be set on success") + require.False(t, target.Status.Conversion.Failed, "Status.Conversion.Failed must be false on success") + require.NotNil(t, target.Status.Conversion.StoredVersion, "StoredVersion must be set") + require.Equal(t, dashv0.VERSION, *target.Status.Conversion.StoredVersion) + }) + + t.Run("Convert_V0_to_V2beta1 must set success status", func(t *testing.T) { + target := &dashv2beta1.Dashboard{} + err := Convert_V0_to_V2beta1(validV0, target, nil, dsProvider, leProvider) + require.NoError(t, err) + require.NotNil(t, target.Status.Conversion, "Status.Conversion must be set on success") + require.False(t, target.Status.Conversion.Failed, "Status.Conversion.Failed must be false on success") + require.NotNil(t, target.Status.Conversion.StoredVersion, "StoredVersion must be set") + require.Equal(t, dashv0.VERSION, *target.Status.Conversion.StoredVersion) + }) +} + +// TestV0ConversionConsistency_ObjectMetaMustBeSetOnError ensures v0 conversion functions +// set ObjectMeta, APIVersion, and Kind on the target even when conversion fails. +func TestV0ConversionConsistency_ObjectMetaMustBeSetOnError(t *testing.T) { + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + leProvider := migrationtestutil.NewLibraryElementProvider() + migration.Initialize(dsProvider, leProvider, migration.DefaultCacheTTL) + + invalidV0 := &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + Name: "test-dashboard", + UID: "test-uid", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 0, + }, + }, + } + + t.Run("Convert_V0_to_V1beta1 must set ObjectMeta on error", func(t *testing.T) { + target := &dashv1.Dashboard{} + _ = Convert_V0_to_V1beta1(invalidV0, target, nil) + require.Equal(t, invalidV0.Name, target.Name, "Name must be set on error") + require.Equal(t, invalidV0.Namespace, target.Namespace, "Namespace must be set on error") + require.Equal(t, dashv1.APIVERSION, target.APIVersion, "APIVersion must be set on error") + }) + + t.Run("Convert_V0_to_V2alpha1 must set ObjectMeta on error", func(t *testing.T) { + target := &dashv2alpha1.Dashboard{} + _ = Convert_V0_to_V2alpha1(invalidV0, target, nil, dsProvider, leProvider) + require.Equal(t, invalidV0.Name, target.Name, "Name must be set on error") + require.Equal(t, invalidV0.Namespace, target.Namespace, "Namespace must be set on error") + require.Equal(t, dashv2alpha1.APIVERSION, target.APIVersion, "APIVersion must be set on error") + }) + + t.Run("Convert_V0_to_V2beta1 must set ObjectMeta on error", func(t *testing.T) { + target := &dashv2beta1.Dashboard{} + _ = Convert_V0_to_V2beta1(invalidV0, target, nil, dsProvider, leProvider) + require.Equal(t, invalidV0.Name, target.Name, "Name must be set on error") + require.Equal(t, invalidV0.Namespace, target.Namespace, "Namespace must be set on error") + require.Equal(t, dashv2beta1.APIVERSION, target.APIVersion, "APIVersion must be set on error") + }) +} + +// TestV0ConversionConsistency_LayoutMustBeSetOnError ensures v2alpha1 and v2beta1 targets +// have a default layout set even when conversion fails to prevent JSON marshaling errors. +func TestV0ConversionConsistency_LayoutMustBeSetOnError(t *testing.T) { + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + leProvider := migrationtestutil.NewLibraryElementProvider() + migration.Initialize(dsProvider, leProvider, migration.DefaultCacheTTL) + + invalidV0 := &dashv0.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + Name: "test-dashboard", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 0, + }, + }, + } + + t.Run("Convert_V0_to_V2alpha1 must set default layout on error", func(t *testing.T) { + target := &dashv2alpha1.Dashboard{} + _ = Convert_V0_to_V2alpha1(invalidV0, target, nil, dsProvider, leProvider) + require.NotNil(t, target.Spec.Layout.GridLayoutKind, "GridLayoutKind must be set on error to prevent JSON marshaling issues") + }) + + t.Run("Convert_V0_to_V2beta1 must set default layout on error", func(t *testing.T) { + target := &dashv2beta1.Dashboard{} + _ = Convert_V0_to_V2beta1(invalidV0, target, nil, dsProvider, leProvider) + require.NotNil(t, target.Spec.Layout.GridLayoutKind, "GridLayoutKind must be set on error to prevent JSON marshaling issues") + }) +} diff --git a/apps/dashboard/pkg/migration/conversion/v1.go b/apps/dashboard/pkg/migration/conversion/v1.go index a6f2836b464..ac538e01e02 100644 --- a/apps/dashboard/pkg/migration/conversion/v1.go +++ b/apps/dashboard/pkg/migration/conversion/v1.go @@ -29,6 +29,9 @@ func Convert_V1beta1_to_V0(in *dashv1.Dashboard, out *dashv0.Dashboard, scope co func Convert_V1beta1_to_V2alpha1(in *dashv1.Dashboard, out *dashv2alpha1.Dashboard, scope conversion.Scope, dsIndexProvider schemaversion.DataSourceIndexProvider, leIndexProvider schemaversion.LibraryElementIndexProvider) error { if err := ConvertDashboard_V1beta1_to_V2alpha1(in, out, scope, dsIndexProvider, leIndexProvider); err != nil { + out.ObjectMeta = in.ObjectMeta + out.APIVersion = dashv2alpha1.APIVERSION + out.Kind = in.Kind out.Status = dashv2alpha1.DashboardStatus{ Conversion: &dashv2alpha1.DashboardConversionStatus{ StoredVersion: ptr.To(dashv1.VERSION), @@ -36,7 +39,6 @@ func Convert_V1beta1_to_V2alpha1(in *dashv1.Dashboard, out *dashv2alpha1.Dashboa Error: ptr.To(err.Error()), }, } - // Don't return error - just set status (matches test expectations and V0 pattern for Convert_V0_to_V2alpha1) // Ensure layout is set even on error to prevent JSON marshaling issues if out.Spec.Layout.GridLayoutKind == nil && out.Spec.Layout.RowsLayoutKind == nil { out.Spec.Layout = dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ @@ -46,7 +48,7 @@ func Convert_V1beta1_to_V2alpha1(in *dashv1.Dashboard, out *dashv2alpha1.Dashboa }, } } - return nil + return err } // We need to make sure the layout is set to some value, otherwise the JSON marshaling will fail. @@ -59,12 +61,23 @@ func Convert_V1beta1_to_V2alpha1(in *dashv1.Dashboard, out *dashv2alpha1.Dashboa } } + // Set successful conversion status + out.Status = dashv2alpha1.DashboardStatus{ + Conversion: &dashv2alpha1.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv1.VERSION), + Failed: false, + }, + } + return nil } func Convert_V1beta1_to_V2beta1(in *dashv1.Dashboard, out *dashv2beta1.Dashboard, scope conversion.Scope, dsIndexProvider schemaversion.DataSourceIndexProvider, leIndexProvider schemaversion.LibraryElementIndexProvider) error { v2alpha1 := &dashv2alpha1.Dashboard{} if err := ConvertDashboard_V1beta1_to_V2alpha1(in, v2alpha1, scope, dsIndexProvider, leIndexProvider); err != nil { + out.ObjectMeta = in.ObjectMeta + out.APIVersion = dashv2beta1.APIVERSION + out.Kind = in.Kind out.Status = dashv2beta1.DashboardStatus{ Conversion: &dashv2beta1.DashboardConversionStatus{ StoredVersion: ptr.To(dashv1.VERSION), @@ -72,10 +85,22 @@ func Convert_V1beta1_to_V2beta1(in *dashv1.Dashboard, out *dashv2beta1.Dashboard Error: ptr.To(err.Error()), }, } + // Ensure layout is set even on error to prevent JSON marshaling issues + if out.Spec.Layout.GridLayoutKind == nil && out.Spec.Layout.RowsLayoutKind == nil { + out.Spec.Layout = dashv2beta1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2beta1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2beta1.DashboardGridLayoutSpec{}, + }, + } + } return err } if err := ConvertDashboard_V2alpha1_to_V2beta1(v2alpha1, out, scope); err != nil { + out.ObjectMeta = in.ObjectMeta + out.APIVersion = dashv2beta1.APIVERSION + out.Kind = in.Kind out.Status = dashv2beta1.DashboardStatus{ Conversion: &dashv2beta1.DashboardConversionStatus{ StoredVersion: ptr.To(dashv1.VERSION), @@ -83,8 +108,25 @@ func Convert_V1beta1_to_V2beta1(in *dashv1.Dashboard, out *dashv2beta1.Dashboard Error: ptr.To(err.Error()), }, } + // Ensure layout is set even on error to prevent JSON marshaling issues + if out.Spec.Layout.GridLayoutKind == nil && out.Spec.Layout.RowsLayoutKind == nil { + out.Spec.Layout = dashv2beta1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2beta1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2beta1.DashboardGridLayoutSpec{}, + }, + } + } return err } + // Set successful conversion status + out.Status = dashv2beta1.DashboardStatus{ + Conversion: &dashv2beta1.DashboardConversionStatus{ + StoredVersion: ptr.To(dashv1.VERSION), + Failed: false, + }, + } + return nil } diff --git a/apps/dashboard/pkg/migration/conversion/v1_test.go b/apps/dashboard/pkg/migration/conversion/v1_test.go index 28cf6e49a3b..6eca186d3ee 100644 --- a/apps/dashboard/pkg/migration/conversion/v1_test.go +++ b/apps/dashboard/pkg/migration/conversion/v1_test.go @@ -21,9 +21,8 @@ func TestV1ConversionErrorHandling(t *testing.T) { leProvider := migrationtestutil.NewLibraryElementProvider() migration.Initialize(dsProvider, leProvider, migration.DefaultCacheTTL) - t.Run("Convert_V1beta1_to_V2alpha1 sets status on conversion error", func(t *testing.T) { - // Create a dashboard that will cause conversion to fail - // We can use an invalid dashboard structure + t.Run("Convert_V1beta1_to_V2alpha1 sets status on successful conversion", func(t *testing.T) { + // Create a simple dashboard that will convert successfully source := &dashv1.Dashboard{ ObjectMeta: metav1.ObjectMeta{ Namespace: "org-1", @@ -31,7 +30,6 @@ func TestV1ConversionErrorHandling(t *testing.T) { Spec: common.Unstructured{ Object: map[string]interface{}{ "title": "test dashboard", - // Missing required fields that might cause conversion to fail }, }, } @@ -39,15 +37,14 @@ func TestV1ConversionErrorHandling(t *testing.T) { err := Convert_V1beta1_to_V2alpha1(source, target, nil, dsProvider, leProvider) - // Convert_V1beta1_to_V2alpha1 doesn't return error, just sets status - require.NoError(t, err, "Convert_V1beta1_to_V2alpha1 doesn't return error") + // Conversion should succeed + require.NoError(t, err) // Layout should always be set require.NotNil(t, target.Spec.Layout.GridLayoutKind) - // If conversion failed, status should be set - if target.Status.Conversion != nil { - require.True(t, target.Status.Conversion.Failed) - require.NotNil(t, target.Status.Conversion.Error) - } + // Status should be set with success + require.NotNil(t, target.Status.Conversion) + require.False(t, target.Status.Conversion.Failed) + require.Equal(t, dashv1.VERSION, *target.Status.Conversion.StoredVersion) }) t.Run("Convert_V1beta1_to_V2beta1 returns error on first step failure", func(t *testing.T) { @@ -126,3 +123,156 @@ func TestV1ConversionErrorHandling(t *testing.T) { } }) } + +// TestV1ConversionConsistency_ErrorsMustBeReturned ensures v1 conversion functions +// return errors instead of swallowing them. This prevents metrics and logs from being silently dropped. +func TestV1ConversionConsistency_ErrorsMustBeReturned(t *testing.T) { + leProvider := migrationtestutil.NewLibraryElementProvider() + + // Create a valid v1 dashboard - the nil dsProvider will cause conversion to fail + validV1 := &dashv1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + Name: "test-dashboard", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 42, + }, + }, + } + + t.Run("Convert_V1beta1_to_V2alpha1 must return error on failure", func(t *testing.T) { + target := &dashv2alpha1.Dashboard{} + // Pass nil dsProvider to cause conversion to fail + err := Convert_V1beta1_to_V2alpha1(validV1, target, nil, nil, leProvider) + require.Error(t, err, "Convert_V1beta1_to_V2alpha1 must return error, not swallow it") + require.True(t, target.Status.Conversion.Failed, "Status.Conversion.Failed must be true") + }) + + t.Run("Convert_V1beta1_to_V2beta1 must return error on failure", func(t *testing.T) { + target := &dashv2beta1.Dashboard{} + // Pass nil dsProvider to cause conversion to fail + err := Convert_V1beta1_to_V2beta1(validV1, target, nil, nil, leProvider) + require.Error(t, err, "Convert_V1beta1_to_V2beta1 must return error, not swallow it") + require.True(t, target.Status.Conversion.Failed, "Status.Conversion.Failed must be true") + }) +} + +// TestV1ConversionConsistency_SuccessStatusMustBeSet ensures v1 conversion functions +// set Status.Conversion with Failed=false on successful conversion. +func TestV1ConversionConsistency_SuccessStatusMustBeSet(t *testing.T) { + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + leProvider := migrationtestutil.NewLibraryElementProvider() + migration.Initialize(dsProvider, leProvider, migration.DefaultCacheTTL) + + // Create a valid v1 dashboard + validV1 := &dashv1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test-dashboard", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 42, + }, + }, + } + + t.Run("Convert_V1beta1_to_V2alpha1 must set success status", func(t *testing.T) { + target := &dashv2alpha1.Dashboard{} + err := Convert_V1beta1_to_V2alpha1(validV1, target, nil, dsProvider, leProvider) + require.NoError(t, err) + require.NotNil(t, target.Status.Conversion, "Status.Conversion must be set on success") + require.False(t, target.Status.Conversion.Failed, "Status.Conversion.Failed must be false on success") + require.NotNil(t, target.Status.Conversion.StoredVersion, "StoredVersion must be set") + require.Equal(t, dashv1.VERSION, *target.Status.Conversion.StoredVersion) + }) + + t.Run("Convert_V1beta1_to_V2beta1 must set success status", func(t *testing.T) { + target := &dashv2beta1.Dashboard{} + err := Convert_V1beta1_to_V2beta1(validV1, target, nil, dsProvider, leProvider) + require.NoError(t, err) + require.NotNil(t, target.Status.Conversion, "Status.Conversion must be set on success") + require.False(t, target.Status.Conversion.Failed, "Status.Conversion.Failed must be false on success") + require.NotNil(t, target.Status.Conversion.StoredVersion, "StoredVersion must be set") + require.Equal(t, dashv1.VERSION, *target.Status.Conversion.StoredVersion) + }) +} + +// TestV1ConversionConsistency_ObjectMetaMustBeSetOnError ensures v1 conversion functions +// set ObjectMeta, APIVersion, and Kind on the target even when conversion fails. +func TestV1ConversionConsistency_ObjectMetaMustBeSetOnError(t *testing.T) { + leProvider := migrationtestutil.NewLibraryElementProvider() + + validV1 := &dashv1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + Name: "test-dashboard", + UID: "test-uid", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 42, + }, + }, + } + + t.Run("Convert_V1beta1_to_V2alpha1 must set ObjectMeta on error", func(t *testing.T) { + target := &dashv2alpha1.Dashboard{} + // Pass nil dsProvider to cause conversion to fail + err := Convert_V1beta1_to_V2alpha1(validV1, target, nil, nil, leProvider) + require.Error(t, err) + require.Equal(t, validV1.Name, target.Name, "Name must be set on error") + require.Equal(t, validV1.Namespace, target.Namespace, "Namespace must be set on error") + require.Equal(t, dashv2alpha1.APIVERSION, target.APIVersion, "APIVersion must be set on error") + }) + + t.Run("Convert_V1beta1_to_V2beta1 must set ObjectMeta on error", func(t *testing.T) { + target := &dashv2beta1.Dashboard{} + // Pass nil dsProvider to cause conversion to fail + err := Convert_V1beta1_to_V2beta1(validV1, target, nil, nil, leProvider) + require.Error(t, err) + require.Equal(t, validV1.Name, target.Name, "Name must be set on error") + require.Equal(t, validV1.Namespace, target.Namespace, "Namespace must be set on error") + require.Equal(t, dashv2beta1.APIVERSION, target.APIVersion, "APIVersion must be set on error") + }) +} + +// TestV1ConversionConsistency_LayoutMustBeSetOnError ensures v2alpha1 and v2beta1 targets +// have a default layout set even when conversion fails to prevent JSON marshaling errors. +func TestV1ConversionConsistency_LayoutMustBeSetOnError(t *testing.T) { + leProvider := migrationtestutil.NewLibraryElementProvider() + + validV1 := &dashv1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "org-1", + Name: "test-dashboard", + }, + Spec: common.Unstructured{ + Object: map[string]interface{}{ + "title": "test dashboard", + "schemaVersion": 42, + }, + }, + } + + t.Run("Convert_V1beta1_to_V2alpha1 must set default layout on error", func(t *testing.T) { + target := &dashv2alpha1.Dashboard{} + // Pass nil dsProvider to cause conversion to fail + err := Convert_V1beta1_to_V2alpha1(validV1, target, nil, nil, leProvider) + require.Error(t, err) + require.NotNil(t, target.Spec.Layout.GridLayoutKind, "GridLayoutKind must be set on error to prevent JSON marshaling issues") + }) + + t.Run("Convert_V1beta1_to_V2beta1 must set default layout on error", func(t *testing.T) { + target := &dashv2beta1.Dashboard{} + // Pass nil dsProvider to cause conversion to fail + err := Convert_V1beta1_to_V2beta1(validV1, target, nil, nil, leProvider) + require.Error(t, err) + require.NotNil(t, target.Spec.Layout.GridLayoutKind, "GridLayoutKind must be set on error to prevent JSON marshaling issues") + }) +} diff --git a/apps/dashboard/pkg/migration/conversion/v2.go b/apps/dashboard/pkg/migration/conversion/v2.go index fa8a49e91b4..beed5679b85 100644 --- a/apps/dashboard/pkg/migration/conversion/v2.go +++ b/apps/dashboard/pkg/migration/conversion/v2.go @@ -26,8 +26,7 @@ func Convert_V2alpha1_to_V0(in *dashv2alpha1.Dashboard, out *dashv0.Dashboard, s Source: in, }, } - // For errors, set status but don't return error - return nil + return err } // Convert v1beta1 → v0 @@ -43,8 +42,7 @@ func Convert_V2alpha1_to_V0(in *dashv2alpha1.Dashboard, out *dashv0.Dashboard, s Source: in, }, } - // For errors, set status but don't return error - return nil + return err } // Update the stored version to reflect the original source @@ -68,9 +66,7 @@ func Convert_V2alpha1_to_V1beta1(in *dashv2alpha1.Dashboard, out *dashv1.Dashboa Source: in, }, } - - // For errors, set status but don't return error - return nil + return err } // Set successful conversion status @@ -129,8 +125,7 @@ func Convert_V2beta1_to_V0(in *dashv2beta1.Dashboard, out *dashv0.Dashboard, sco Source: in, }, } - // For errors, set status but don't return error - return nil + return err } // Convert v1beta1 → v0 @@ -146,7 +141,7 @@ func Convert_V2beta1_to_V0(in *dashv2beta1.Dashboard, out *dashv0.Dashboard, sco Source: in, }, } - return nil + return err } // Update the stored version to reflect the original source @@ -172,8 +167,7 @@ func Convert_V2beta1_to_V1beta1(in *dashv2beta1.Dashboard, out *dashv1.Dashboard Source: in, }, } - // For errors, set status but don't return error - return nil + return err } // Convert v2alpha1 → v1beta1 @@ -188,8 +182,7 @@ func Convert_V2beta1_to_V1beta1(in *dashv2beta1.Dashboard, out *dashv1.Dashboard Source: in, }, } - // For errors, set status but don't return error - return nil + return err } // Set successful conversion status diff --git a/apps/dashboard/pkg/migration/conversion/v2_test.go b/apps/dashboard/pkg/migration/conversion/v2_test.go index d2010062223..3e218da9f81 100644 --- a/apps/dashboard/pkg/migration/conversion/v2_test.go +++ b/apps/dashboard/pkg/migration/conversion/v2_test.go @@ -186,3 +186,141 @@ func TestV2beta1ConversionErrorHandling(t *testing.T) { } }) } + +// TestV2ConversionConsistency_SuccessStatusMustBeSet ensures v2 conversion functions +// set Status.Conversion with Failed=false on successful conversion. +func TestV2ConversionConsistency_SuccessStatusMustBeSet(t *testing.T) { + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + leProvider := migrationtestutil.NewLibraryElementProvider() + migration.Initialize(dsProvider, leProvider, migration.DefaultCacheTTL) + + // Create valid v2alpha1 dashboard + validV2alpha1 := &dashv2alpha1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test-dashboard", + }, + Spec: dashv2alpha1.DashboardSpec{ + Title: "test dashboard", + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{}, + }, + }, + }, + } + + // Create valid v2beta1 dashboard + validV2beta1 := &dashv2beta1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test-dashboard", + }, + Spec: dashv2beta1.DashboardSpec{ + Title: "test dashboard", + Layout: dashv2beta1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2beta1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2beta1.DashboardGridLayoutSpec{}, + }, + }, + }, + } + + t.Run("Convert_V2alpha1_to_V1beta1 must set success status", func(t *testing.T) { + target := &dashv1.Dashboard{} + err := Convert_V2alpha1_to_V1beta1(validV2alpha1, target, nil) + require.NoError(t, err) + require.NotNil(t, target.Status.Conversion, "Status.Conversion must be set on success") + require.False(t, target.Status.Conversion.Failed, "Status.Conversion.Failed must be false on success") + require.NotNil(t, target.Status.Conversion.StoredVersion, "StoredVersion must be set") + }) + + t.Run("Convert_V2alpha1_to_V2beta1 must set success status", func(t *testing.T) { + target := &dashv2beta1.Dashboard{} + err := Convert_V2alpha1_to_V2beta1(validV2alpha1, target, nil) + require.NoError(t, err) + require.NotNil(t, target.Status.Conversion, "Status.Conversion must be set on success") + require.False(t, target.Status.Conversion.Failed, "Status.Conversion.Failed must be false on success") + require.NotNil(t, target.Status.Conversion.StoredVersion, "StoredVersion must be set") + }) + + t.Run("Convert_V2beta1_to_V1beta1 must set success status", func(t *testing.T) { + target := &dashv1.Dashboard{} + err := Convert_V2beta1_to_V1beta1(validV2beta1, target, nil, dsProvider) + require.NoError(t, err) + require.NotNil(t, target.Status.Conversion, "Status.Conversion must be set on success") + require.False(t, target.Status.Conversion.Failed, "Status.Conversion.Failed must be false on success") + require.NotNil(t, target.Status.Conversion.StoredVersion, "StoredVersion must be set") + }) + + t.Run("Convert_V2beta1_to_V2alpha1 must set success status", func(t *testing.T) { + target := &dashv2alpha1.Dashboard{} + err := Convert_V2beta1_to_V2alpha1(validV2beta1, target, nil) + require.NoError(t, err) + require.NotNil(t, target.Status.Conversion, "Status.Conversion must be set on success") + require.False(t, target.Status.Conversion.Failed, "Status.Conversion.Failed must be false on success") + require.NotNil(t, target.Status.Conversion.StoredVersion, "StoredVersion must be set") + }) +} + +// TestV2ConversionConsistency_ErrorsMustBeReturned ensures v2 conversion functions +// return errors instead of swallowing them. This prevents metrics and logs from being silently dropped. +func TestV2ConversionConsistency_ErrorsMustBeReturned(t *testing.T) { + dsProvider := migrationtestutil.NewDataSourceProvider(migrationtestutil.StandardTestConfig) + leProvider := migrationtestutil.NewLibraryElementProvider() + migration.Initialize(dsProvider, leProvider, migration.DefaultCacheTTL) + + // Note: v2 conversions are harder to make fail since they don't go through schema migration. + // These tests verify that IF an error occurs, it is returned (not swallowed). + // The existing tests already cover this, but we add explicit assertions here. + + t.Run("Convert_V2alpha1_to_V2beta1 returns error on conversion failure", func(t *testing.T) { + // Valid dashboard should succeed + source := &dashv2alpha1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test-dashboard", + }, + Spec: dashv2alpha1.DashboardSpec{ + Title: "test dashboard", + Layout: dashv2alpha1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2alpha1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2alpha1.DashboardGridLayoutSpec{}, + }, + }, + }, + } + target := &dashv2beta1.Dashboard{} + err := Convert_V2alpha1_to_V2beta1(source, target, nil) + // This should succeed, verifying the function works + require.NoError(t, err) + require.False(t, target.Status.Conversion.Failed) + }) + + t.Run("Convert_V2beta1_to_V2alpha1 returns error on conversion failure", func(t *testing.T) { + // Valid dashboard should succeed + source := &dashv2beta1.Dashboard{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "test-dashboard", + }, + Spec: dashv2beta1.DashboardSpec{ + Title: "test dashboard", + Layout: dashv2beta1.DashboardGridLayoutKindOrRowsLayoutKindOrAutoGridLayoutKindOrTabsLayoutKind{ + GridLayoutKind: &dashv2beta1.DashboardGridLayoutKind{ + Kind: "GridLayout", + Spec: dashv2beta1.DashboardGridLayoutSpec{}, + }, + }, + }, + } + target := &dashv2alpha1.Dashboard{} + err := Convert_V2beta1_to_V2alpha1(source, target, nil) + // This should succeed, verifying the function works + require.NoError(t, err) + require.False(t, target.Status.Conversion.Failed) + }) +} diff --git a/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v1beta1.go b/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v1beta1.go index fed2691db27..73fd97b2089 100644 --- a/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v1beta1.go +++ b/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v1beta1.go @@ -1059,7 +1059,8 @@ func convertPanelKindToV1(panelKind *dashv2alpha1.DashboardPanelKind, panel map[ } // Convert queries (targets) - targets := make([]map[string]interface{}, 0, len(spec.Data.Spec.Queries)) + // Use []interface{} for consistency with JSON unmarshaling and other code paths + targets := make([]interface{}, 0, len(spec.Data.Spec.Queries)) for _, query := range spec.Data.Spec.Queries { target := convertPanelQueryToV1(&query) targets = append(targets, target) From 439d2c806c0db5433a4279029984e8b862cb8c13 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Thu, 11 Dec 2025 09:30:55 +0100 Subject: [PATCH 008/297] Alerting: Add folder_uid label to the grafana_alerting_rule_group_rules metric (#115129) --- pkg/services/ngalert/metrics/scheduler.go | 2 +- pkg/services/ngalert/schedule/metrics.go | 2 +- .../ngalert/schedule/schedule_unit_test.go | 48 +++++++++---------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/pkg/services/ngalert/metrics/scheduler.go b/pkg/services/ngalert/metrics/scheduler.go index d6c6707f0a9..9bcff59ffb9 100644 --- a/pkg/services/ngalert/metrics/scheduler.go +++ b/pkg/services/ngalert/metrics/scheduler.go @@ -130,7 +130,7 @@ func NewSchedulerMetrics(r prometheus.Registerer) *Scheduler { Name: "rule_group_rules", Help: "The number of alert rules that are scheduled, by type and state.", }, - []string{"org", "type", "state", "rule_group"}, + []string{"org", "type", "state", "rule_group", "folder_uid"}, ), Groups: promauto.With(r).NewGaugeVec( prometheus.GaugeOpts{ diff --git a/pkg/services/ngalert/schedule/metrics.go b/pkg/services/ngalert/schedule/metrics.go index e3281c33710..f4630b39cad 100644 --- a/pkg/services/ngalert/schedule/metrics.go +++ b/pkg/services/ngalert/schedule/metrics.go @@ -117,7 +117,7 @@ func (sch *schedule) updateRulesMetrics(alertRules []*models.AlertRule) { // Set metrics for key, count := range buckets { - sch.metrics.GroupRules.WithLabelValues(fmt.Sprint(key.orgID), key.ruleType.String(), key.state, makeRuleGroupLabelValue(key.ruleGroup)).Set(float64(count)) + sch.metrics.GroupRules.WithLabelValues(fmt.Sprint(key.orgID), key.ruleType.String(), key.state, makeRuleGroupLabelValue(key.ruleGroup), key.ruleGroup.NamespaceUID).Set(float64(count)) } for orgID, numRulesNfSettings := range orgsNfSettings { sch.metrics.SimpleNotificationRules.WithLabelValues(fmt.Sprint(orgID)).Set(float64(numRulesNfSettings)) diff --git a/pkg/services/ngalert/schedule/schedule_unit_test.go b/pkg/services/ngalert/schedule/schedule_unit_test.go index fc402bcc793..bbfa20fceb1 100644 --- a/pkg/services/ngalert/schedule/schedule_unit_test.go +++ b/pkg/services/ngalert/schedule/schedule_unit_test.go @@ -157,8 +157,8 @@ func TestProcessTicks(t *testing.T) { expectedMetric := fmt.Sprintf( `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, by type and state. # TYPE grafana_alerting_rule_group_rules gauge - grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active",type="alerting"} 1 - `, alertRule1.OrgID, folderWithRuleGroup1) + grafana_alerting_rule_group_rules{folder_uid="%[3]s",org="%[1]d",rule_group="%[2]s",state="active",type="alerting"} 1 + `, alertRule1.OrgID, folderWithRuleGroup1, alertRule1.NamespaceUID) err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") require.NoError(t, err) @@ -199,9 +199,9 @@ func TestProcessTicks(t *testing.T) { expectedMetric := fmt.Sprintf( `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, by type and state. # TYPE grafana_alerting_rule_group_rules gauge - grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active",type="alerting"} 1 - grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="active",type="alerting"} 1 - `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2) + grafana_alerting_rule_group_rules{folder_uid="%[4]s",org="%[1]d",rule_group="%[2]s",state="active",type="alerting"} 1 + grafana_alerting_rule_group_rules{folder_uid="%[5]s",org="%[1]d",rule_group="%[3]s",state="active",type="alerting"} 1 + `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2, alertRule1.NamespaceUID, alertRule2.NamespaceUID) err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") require.NoError(t, err) @@ -265,9 +265,9 @@ func TestProcessTicks(t *testing.T) { expectedMetric := fmt.Sprintf( `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, by type and state. # TYPE grafana_alerting_rule_group_rules gauge - grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="paused",type="alerting"} 1 - grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="active",type="alerting"} 1 - `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2) + grafana_alerting_rule_group_rules{folder_uid="%[4]s",org="%[1]d",rule_group="%[2]s",state="paused",type="alerting"} 1 + grafana_alerting_rule_group_rules{folder_uid="%[5]s",org="%[1]d",rule_group="%[3]s",state="active",type="alerting"} 1 + `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2, alertRule1.NamespaceUID, alertRule2.NamespaceUID) err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") require.NoError(t, err) @@ -308,10 +308,10 @@ func TestProcessTicks(t *testing.T) { expectedMetric := fmt.Sprintf( `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, by type and state. # TYPE grafana_alerting_rule_group_rules gauge - grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="paused",type="alerting"} 1 - grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="paused",type="alerting"} 1 + grafana_alerting_rule_group_rules{folder_uid="%[4]s",org="%[1]d",rule_group="%[2]s",state="paused",type="alerting"} 1 + grafana_alerting_rule_group_rules{folder_uid="%[5]s",org="%[1]d",rule_group="%[3]s",state="paused",type="alerting"} 1 - `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2) + `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2, alertRule1.NamespaceUID, alertRule2.NamespaceUID) err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") require.NoError(t, err) }) @@ -336,9 +336,9 @@ func TestProcessTicks(t *testing.T) { expectedMetric := fmt.Sprintf( `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, by type and state. # TYPE grafana_alerting_rule_group_rules gauge - grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active",type="alerting"} 1 - grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="active",type="alerting"} 1 - `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2) + grafana_alerting_rule_group_rules{folder_uid="%[4]s",org="%[1]d",rule_group="%[2]s",state="active",type="alerting"} 1 + grafana_alerting_rule_group_rules{folder_uid="%[5]s",org="%[1]d",rule_group="%[3]s",state="active",type="alerting"} 1 + `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2, alertRule1.NamespaceUID, alertRule2.NamespaceUID) err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") require.NoError(t, err) @@ -363,8 +363,8 @@ func TestProcessTicks(t *testing.T) { expectedMetric := fmt.Sprintf( `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, by type and state. # TYPE grafana_alerting_rule_group_rules gauge - grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active",type="alerting"} 1 - `, alertRule1.OrgID, folderWithRuleGroup2) + grafana_alerting_rule_group_rules{folder_uid="%[3]s",org="%[1]d",rule_group="%[2]s",state="active",type="alerting"} 1 + `, alertRule1.OrgID, folderWithRuleGroup2, alertRule2.NamespaceUID) err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") require.NoError(t, err) }) @@ -684,8 +684,8 @@ func TestSchedule_updateRulesMetrics(t *testing.T) { expectedMetric := fmt.Sprintf( `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, by type and state. # TYPE grafana_alerting_rule_group_rules gauge - grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active", type="alerting"} 1 - `, alertRule1.OrgID, folderWithRuleGroup1) + grafana_alerting_rule_group_rules{folder_uid="%[3]s",org="%[1]d",rule_group="%[2]s",state="active",type="alerting"} 1 + `, alertRule1.OrgID, folderWithRuleGroup1, alertRule1.NamespaceUID) err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") require.NoError(t, err) @@ -705,9 +705,9 @@ func TestSchedule_updateRulesMetrics(t *testing.T) { expectedMetric := fmt.Sprintf( `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, by type and state. # TYPE grafana_alerting_rule_group_rules gauge - grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active",type="alerting"} 1 - grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="active",type="alerting"} 1 - `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2) + grafana_alerting_rule_group_rules{folder_uid="%[4]s",org="%[1]d",rule_group="%[2]s",state="active",type="alerting"} 1 + grafana_alerting_rule_group_rules{folder_uid="%[5]s",org="%[1]d",rule_group="%[3]s",state="active",type="alerting"} 1 + `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2, alertRule1.NamespaceUID, alertRule2.NamespaceUID) err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") require.NoError(t, err) @@ -720,9 +720,9 @@ func TestSchedule_updateRulesMetrics(t *testing.T) { expectedMetric := fmt.Sprintf( `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, by type and state. # TYPE grafana_alerting_rule_group_rules gauge - grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active",type="alerting"} 1 - grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="active",type="alerting"} 1 - `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2) + grafana_alerting_rule_group_rules{folder_uid="%[4]s",org="%[1]d",rule_group="%[2]s",state="active",type="alerting"} 1 + grafana_alerting_rule_group_rules{folder_uid="%[5]s",org="%[1]d",rule_group="%[3]s",state="active",type="alerting"} 1 + `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2, alertRule1.NamespaceUID, alertRule2.NamespaceUID) err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") require.NoError(t, err) From d48455cd20201611ca4fb67c19db51f24818f63e Mon Sep 17 00:00:00 2001 From: Victor Marin Date: Thu, 11 Dec 2025 11:31:19 +0200 Subject: [PATCH 009/297] Dashboards: Panel non applicable filters optimization (#115132) optimisations --- .../scene/PanelNonApplicableDrilldownsSubHeader.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/PanelNonApplicableDrilldownsSubHeader.tsx b/public/app/features/dashboard-scene/scene/PanelNonApplicableDrilldownsSubHeader.tsx index fe8437f9dc5..2f04d91fe90 100644 --- a/public/app/features/dashboard-scene/scene/PanelNonApplicableDrilldownsSubHeader.tsx +++ b/public/app/features/dashboard-scene/scene/PanelNonApplicableDrilldownsSubHeader.tsx @@ -79,16 +79,8 @@ export function PanelNonApplicableDrilldownsSubHeader({ filtersVar, groupByVar, }; fetchApplicability(); - }, [ - filterKey, - groupByKey, - filtersVar, - groupByVar, - queryRunner, - filtersState?.filters, - groupByState?.value, - filtersState?.originFilters, - ]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filterKey, groupByKey, filtersVar, groupByVar, queryRunner]); useLayoutEffect(() => { if (!nonApplicable.length) { From e2d12f4cce4c2bbdb4447b61bbdd38612f0c09de Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 11 Dec 2025 10:55:12 +0100 Subject: [PATCH 010/297] Zanzana: Refactor remote client initialization (#114142) * Zanzana: Refactor remote client * rename config field URL to Addr * Instrument grpc queries * fix duplicated field --- apps/iam/pkg/app/app.go | 5 +- apps/iam/pkg/reconcilers/folder_reconciler.go | 5 +- .../iam/zanzana_folder_reconciler.go | 9 ++-- pkg/services/authz/rbac.go | 2 +- pkg/services/authz/zanzana.go | 51 +++++++++++-------- pkg/setting/settings_zanzana.go | 15 ++++-- 6 files changed, 55 insertions(+), 32 deletions(-) diff --git a/apps/iam/pkg/app/app.go b/apps/iam/pkg/app/app.go index 05216220d7e..ff2a0cc7feb 100644 --- a/apps/iam/pkg/app/app.go +++ b/apps/iam/pkg/app/app.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/prometheus/client_golang/prometheus" + "github.com/grafana/grafana-app-sdk/app" "github.com/grafana/grafana-app-sdk/logging" "github.com/grafana/grafana-app-sdk/operator" @@ -12,7 +14,6 @@ import ( foldersKind "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" "github.com/grafana/grafana/apps/iam/pkg/reconcilers" "github.com/grafana/grafana/pkg/services/authz" - "github.com/prometheus/client_golang/prometheus" ) var appManifestData = app.ManifestData{ @@ -78,7 +79,7 @@ func New(cfg app.Config) (app.App, error) { folderReconciler, err := reconcilers.NewFolderReconciler(reconcilers.ReconcilerConfig{ ZanzanaCfg: appSpecificConfig.ZanzanaClientCfg, Metrics: metrics, - }) + }, appSpecificConfig.MetricsRegisterer) if err != nil { return nil, fmt.Errorf("unable to create FolderReconciler: %w", err) } diff --git a/apps/iam/pkg/reconcilers/folder_reconciler.go b/apps/iam/pkg/reconcilers/folder_reconciler.go index 066637ee1e6..66a6868722d 100644 --- a/apps/iam/pkg/reconcilers/folder_reconciler.go +++ b/apps/iam/pkg/reconcilers/folder_reconciler.go @@ -5,6 +5,7 @@ import ( "fmt" "time" + "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" @@ -35,9 +36,9 @@ type FolderReconciler struct { metrics *ReconcilerMetrics } -func NewFolderReconciler(cfg ReconcilerConfig) (operator.Reconciler, error) { +func NewFolderReconciler(cfg ReconcilerConfig, reg prometheus.Registerer) (operator.Reconciler, error) { // Create Zanzana client - zanzanaClient, err := authz.NewRemoteZanzanaClient("*", cfg.ZanzanaCfg) + zanzanaClient, err := authz.NewRemoteZanzanaClient(cfg.ZanzanaCfg, reg) if err != nil { return nil, fmt.Errorf("unable to create zanzana client: %w", err) diff --git a/pkg/operators/iam/zanzana_folder_reconciler.go b/pkg/operators/iam/zanzana_folder_reconciler.go index 0f2fb287bb4..ee2233d7877 100644 --- a/pkg/operators/iam/zanzana_folder_reconciler.go +++ b/pkg/operators/iam/zanzana_folder_reconciler.go @@ -11,15 +11,16 @@ import ( "os/signal" "syscall" + "github.com/prometheus/client_golang/prometheus" + "k8s.io/client-go/rest" + "k8s.io/client-go/transport" + "github.com/grafana/grafana-app-sdk/logging" "github.com/grafana/grafana-app-sdk/operator" folder "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" "github.com/grafana/grafana/apps/iam/pkg/app" "github.com/grafana/grafana/pkg/server" "github.com/grafana/grafana/pkg/setting" - "github.com/prometheus/client_golang/prometheus" - "k8s.io/client-go/rest" - "k8s.io/client-go/transport" "github.com/grafana/authlib/authn" utilnet "k8s.io/apimachinery/pkg/util/net" @@ -95,7 +96,7 @@ func buildIAMConfigFromSettings(cfg *setting.Cfg, registerer prometheus.Register if zanzanaURL == "" { return nil, fmt.Errorf("zanzana_url is required in [operator] section") } - iamCfg.AppConfig.ZanzanaClientCfg.URL = zanzanaURL + iamCfg.AppConfig.ZanzanaClientCfg.Addr = zanzanaURL iamCfg.AppConfig.InformerConfig.MaxConcurrentWorkers = operatorSec.Key("max_concurrent_workers").MustUint64(20) diff --git a/pkg/services/authz/rbac.go b/pkg/services/authz/rbac.go index 23de10511a2..c6c078a9b4a 100644 --- a/pkg/services/authz/rbac.go +++ b/pkg/services/authz/rbac.go @@ -152,7 +152,7 @@ func ProvideStandaloneAuthZClient( //nolint:staticcheck // not yet migrated to OpenFeature zanzanaEnabled := features.IsEnabledGlobally(featuremgmt.FlagZanzana) - zanzanaClient, err := ProvideStandaloneZanzanaClient(cfg, features) + zanzanaClient, err := ProvideStandaloneZanzanaClient(cfg, features, reg) if err != nil { return nil, err } diff --git a/pkg/services/authz/zanzana.go b/pkg/services/authz/zanzana.go index da77010d0eb..f6751258f2d 100644 --- a/pkg/services/authz/zanzana.go +++ b/pkg/services/authz/zanzana.go @@ -4,16 +4,19 @@ import ( "context" "errors" "fmt" + "time" "github.com/fullstorydev/grpchan/inprocgrpc" authnlib "github.com/grafana/authlib/authn" authzv1 "github.com/grafana/authlib/authz/proto/v1" "github.com/grafana/authlib/grpcutils" "github.com/grafana/authlib/types" + "github.com/grafana/dskit/middleware" "github.com/grafana/dskit/services" grpcAuth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" openfgav1 "github.com/openfga/api/proto/openfga/v1" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" @@ -43,14 +46,14 @@ func ProvideZanzanaClient(cfg *setting.Cfg, db db.DB, tracer tracing.Tracer, fea switch cfg.ZanzanaClient.Mode { case setting.ZanzanaModeClient: - return NewRemoteZanzanaClient( - fmt.Sprintf("stacks-%s", cfg.StackID), - ZanzanaClientConfig{ - URL: cfg.ZanzanaClient.Addr, - Token: cfg.ZanzanaClient.Token, - TokenExchangeURL: cfg.ZanzanaClient.TokenExchangeURL, - ServerCertFile: cfg.ZanzanaClient.ServerCertFile, - }) + zanzanaConfig := ZanzanaClientConfig{ + Addr: cfg.ZanzanaClient.Addr, + Token: cfg.ZanzanaClient.Token, + TokenExchangeURL: cfg.ZanzanaClient.TokenExchangeURL, + TokenNamespace: cfg.ZanzanaClient.TokenNamespace, + ServerCertFile: cfg.ZanzanaClient.ServerCertFile, + } + return NewRemoteZanzanaClient(zanzanaConfig, reg) case setting.ZanzanaModeEmbedded: logger := log.New("zanzana.server") @@ -97,32 +100,33 @@ func ProvideZanzanaClient(cfg *setting.Cfg, db db.DB, tracer tracing.Tracer, fea // ProvideStandaloneZanzanaClient provides a standalone Zanzana client, without registering the Zanzana service. // Client connects to a remote Zanzana server specified in the configuration. -func ProvideStandaloneZanzanaClient(cfg *setting.Cfg, features featuremgmt.FeatureToggles) (zanzana.Client, error) { +func ProvideStandaloneZanzanaClient(cfg *setting.Cfg, features featuremgmt.FeatureToggles, reg prometheus.Registerer) (zanzana.Client, error) { //nolint:staticcheck // not yet migrated to OpenFeature if !features.IsEnabledGlobally(featuremgmt.FlagZanzana) { return zClient.NewNoopClient(), nil } zanzanaConfig := ZanzanaClientConfig{ - URL: cfg.ZanzanaClient.Addr, + Addr: cfg.ZanzanaClient.Addr, Token: cfg.ZanzanaClient.Token, TokenExchangeURL: cfg.ZanzanaClient.TokenExchangeURL, + TokenNamespace: cfg.ZanzanaClient.TokenNamespace, ServerCertFile: cfg.ZanzanaClient.ServerCertFile, } - return NewRemoteZanzanaClient(cfg.ZanzanaClient.TokenNamespace, zanzanaConfig) + return NewRemoteZanzanaClient(zanzanaConfig, reg) } type ZanzanaClientConfig struct { - URL string + Addr string Token string TokenExchangeURL string - ServerCertFile string TokenNamespace string + ServerCertFile string } // NewRemoteZanzanaClient creates a new Zanzana client that connects to remote Zanzana server. -func NewRemoteZanzanaClient(namespace string, cfg ZanzanaClientConfig) (zanzana.Client, error) { +func NewRemoteZanzanaClient(cfg ZanzanaClientConfig, reg prometheus.Registerer) (zanzana.Client, error) { tokenClient, err := authnlib.NewTokenExchangeClient(authnlib.TokenExchangeConfig{ Token: cfg.Token, TokenExchangeURL: cfg.TokenExchangeURL, @@ -139,18 +143,25 @@ func NewRemoteZanzanaClient(namespace string, cfg ZanzanaClientConfig) (zanzana. } } + authzRequestDuration := promauto.With(reg).NewHistogramVec(prometheus.HistogramOpts{ + Name: "authz_zanzana_client_request_duration_seconds", + Help: "Time spent executing requests to zanzana server.", + NativeHistogramBucketFactor: 1.1, + NativeHistogramMaxBucketNumber: 160, + NativeHistogramMinResetDuration: time.Hour, + }, []string{"operation", "status_code"}) + unaryInterceptors, streamInterceptors := instrument(authzRequestDuration, middleware.ReportGRPCStatusOption) + dialOptions := []grpc.DialOption{ grpc.WithTransportCredentials(transportCredentials), grpc.WithPerRPCCredentials( - NewGRPCTokenAuth( - AuthzServiceAudience, - namespace, - tokenClient, - ), + NewGRPCTokenAuth(AuthzServiceAudience, cfg.TokenNamespace, tokenClient), ), + grpc.WithChainUnaryInterceptor(unaryInterceptors...), + grpc.WithChainStreamInterceptor(streamInterceptors...), } - conn, err := grpc.NewClient(cfg.URL, dialOptions...) + conn, err := grpc.NewClient(cfg.Addr, dialOptions...) if err != nil { return nil, fmt.Errorf("failed to create zanzana client to remote server: %w", err) } diff --git a/pkg/setting/settings_zanzana.go b/pkg/setting/settings_zanzana.go index 1a06ea034ef..d30304e4bd0 100644 --- a/pkg/setting/settings_zanzana.go +++ b/pkg/setting/settings_zanzana.go @@ -110,15 +110,24 @@ func (cfg *Cfg) readZanzanaSettings() { zc.Mode = "embedded" } - zc.Token = clientSec.Key("token").MustString("") - zc.TokenExchangeURL = clientSec.Key("token_exchange_url").MustString("") zc.Addr = clientSec.Key("address").MustString("") zc.ServerCertFile = clientSec.Key("tls_cert").MustString("") - // TODO: read Token and TokenExchangeURL from grpc_client_authentication section grpcClientAuthSection := cfg.SectionWithEnvOverrides("grpc_client_authentication") + zc.Token = grpcClientAuthSection.Key("token").MustString("") + zc.TokenExchangeURL = grpcClientAuthSection.Key("token_exchange_url").MustString("") zc.TokenNamespace = grpcClientAuthSection.Key("token_namespace").MustString("stacks-" + cfg.StackID) + // TODO: remove old settings when migrated + token := clientSec.Key("token").MustString("") + tokenExchangeURL := clientSec.Key("token_exchange_url").MustString("") + if token != "" { + zc.Token = token + } + if tokenExchangeURL != "" { + zc.TokenExchangeURL = tokenExchangeURL + } + cfg.ZanzanaClient = zc zs := ZanzanaServerSettings{} From 7fe3214f166cda0d57d2dcf40bb28a12c8e02a2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Jim=C3=A9nez=20S=C3=A1nchez?= Date: Thu, 11 Dec 2025 11:36:01 +0100 Subject: [PATCH 011/297] Provisioning: Add fieldSelector regression tests for Repository and Jobs (#115135) --- .../apis/provisioning/fieldselector_test.go | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 pkg/tests/apis/provisioning/fieldselector_test.go diff --git a/pkg/tests/apis/provisioning/fieldselector_test.go b/pkg/tests/apis/provisioning/fieldselector_test.go new file mode 100644 index 00000000000..78f05eeacb7 --- /dev/null +++ b/pkg/tests/apis/provisioning/fieldselector_test.go @@ -0,0 +1,211 @@ +package provisioning + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + "github.com/grafana/grafana/pkg/util/testutil" +) + +// TestIntegrationProvisioning_RepositoryFieldSelector tests that fieldSelector +// works correctly for Repository resources. This prevents regression where +// fieldSelector=metadata.name= was not working properly. +func TestIntegrationProvisioning_RepositoryFieldSelector(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + helper := runGrafana(t) + ctx := context.Background() + + // Create multiple repositories for testing + repo1Name := "repo-selector-test-1" + repo2Name := "repo-selector-test-2" + repo3Name := "repo-selector-test-3" + + // Create first repository + repo1 := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{ + "Name": repo1Name, + "SyncEnabled": false, // Disable sync to speed up test + }) + _, err := helper.Repositories.Resource.Create(ctx, repo1, metav1.CreateOptions{}) + require.NoError(t, err, "failed to create first repository") + helper.WaitForHealthyRepository(t, repo1Name) + + // Create second repository + repo2 := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{ + "Name": repo2Name, + "SyncEnabled": false, + }) + _, err = helper.Repositories.Resource.Create(ctx, repo2, metav1.CreateOptions{}) + require.NoError(t, err, "failed to create second repository") + helper.WaitForHealthyRepository(t, repo2Name) + + // Create third repository + repo3 := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{ + "Name": repo3Name, + "SyncEnabled": false, + }) + _, err = helper.Repositories.Resource.Create(ctx, repo3, metav1.CreateOptions{}) + require.NoError(t, err, "failed to create third repository") + helper.WaitForHealthyRepository(t, repo3Name) + + // Verify all repositories were created + allRepos, err := helper.Repositories.Resource.List(ctx, metav1.ListOptions{}) + require.NoError(t, err, "should be able to list all repositories") + require.GreaterOrEqual(t, len(allRepos.Items), 3, "should have at least 3 repositories") + + t.Run("should filter by metadata.name and return single repository", func(t *testing.T) { + list, err := helper.Repositories.Resource.List(ctx, metav1.ListOptions{ + FieldSelector: "metadata.name=" + repo2Name, + }) + require.NoError(t, err, "fieldSelector query should succeed") + require.Len(t, list.Items, 1, "should return exactly one repository") + require.Equal(t, repo2Name, list.Items[0].GetName(), "should return the correct repository") + }) + + t.Run("should filter by different metadata.name", func(t *testing.T) { + list, err := helper.Repositories.Resource.List(ctx, metav1.ListOptions{ + FieldSelector: "metadata.name=" + repo1Name, + }) + require.NoError(t, err, "fieldSelector query should succeed") + require.Len(t, list.Items, 1, "should return exactly one repository") + require.Equal(t, repo1Name, list.Items[0].GetName(), "should return the first repository") + }) + + t.Run("should return empty when fieldSelector does not match any repository", func(t *testing.T) { + list, err := helper.Repositories.Resource.List(ctx, metav1.ListOptions{ + FieldSelector: "metadata.name=non-existent-repository", + }) + require.NoError(t, err, "fieldSelector query should succeed even with no matches") + require.Empty(t, list.Items, "should return empty list when no repositories match") + }) + + t.Run("listing without fieldSelector should return all repositories", func(t *testing.T) { + list, err := helper.Repositories.Resource.List(ctx, metav1.ListOptions{}) + require.NoError(t, err, "should be able to list without fieldSelector") + require.GreaterOrEqual(t, len(list.Items), 3, "should return all repositories when no filter is applied") + + // Verify our test repositories are in the list + names := make(map[string]bool) + for _, item := range list.Items { + names[item.GetName()] = true + } + require.True(t, names[repo1Name], "should contain repo1") + require.True(t, names[repo2Name], "should contain repo2") + require.True(t, names[repo3Name], "should contain repo3") + }) +} + +// TestIntegrationProvisioning_JobFieldSelector tests that fieldSelector +// works correctly for Job resources. This prevents regression where +// fieldSelector=metadata.name= was not working properly. +func TestIntegrationProvisioning_JobFieldSelector(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + helper := runGrafana(t) + ctx := context.Background() + + // Create a repository to trigger jobs + repoName := "job-selector-test-repo" + repo := helper.RenderObject(t, "testdata/local-write.json.tmpl", map[string]any{ + "Name": repoName, + "SyncEnabled": false, + }) + _, err := helper.Repositories.Resource.Create(ctx, repo, metav1.CreateOptions{}) + require.NoError(t, err, "failed to create repository") + helper.WaitForHealthyRepository(t, repoName) + + // Copy some test files to trigger jobs + helper.CopyToProvisioningPath(t, "testdata/all-panels.json", "job-test-dashboard-1.json") + helper.CopyToProvisioningPath(t, "testdata/text-options.json", "job-test-dashboard-2.json") + + // Trigger multiple jobs to have multiple job resources + job1Spec := provisioning.JobSpec{ + Action: provisioning.JobActionPull, + Pull: &provisioning.SyncJobOptions{}, + } + + // Trigger first job + body1 := asJSON(job1Spec) + result1 := helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repoName). + SubResource("jobs"). + Body(body1). + SetHeader("Content-Type", "application/json"). + Do(ctx) + require.NoError(t, result1.Error(), "should be able to trigger first job") + + obj1, err := result1.Get() + require.NoError(t, err, "should get first job object") + job1 := obj1.(*unstructured.Unstructured) + job1Name := job1.GetName() + require.NotEmpty(t, job1Name, "first job should have a name") + + // Wait for first job to complete before starting second + helper.AwaitJobs(t, repoName) + + // Trigger second job + result2 := helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repoName). + SubResource("jobs"). + Body(body1). + SetHeader("Content-Type", "application/json"). + Do(ctx) + require.NoError(t, result2.Error(), "should be able to trigger second job") + + obj2, err := result2.Get() + require.NoError(t, err, "should get second job object") + job2 := obj2.(*unstructured.Unstructured) + job2Name := job2.GetName() + require.NotEmpty(t, job2Name, "second job should have a name") + + t.Run("should filter by metadata.name and return single job", func(t *testing.T) { + // Note: Jobs are ephemeral and may complete quickly, so we test while they exist + list, err := helper.Jobs.Resource.List(ctx, metav1.ListOptions{ + FieldSelector: "metadata.name=" + job2Name, + }) + require.NoError(t, err, "fieldSelector query should succeed") + + // The job might have completed already, but if it exists, it should be the only one + if len(list.Items) > 0 { + require.Len(t, list.Items, 1, "should return at most one job") + require.Equal(t, job2Name, list.Items[0].GetName(), "should return the correct job") + } + }) + + t.Run("should filter by different metadata.name", func(t *testing.T) { + list, err := helper.Jobs.Resource.List(ctx, metav1.ListOptions{ + FieldSelector: "metadata.name=" + job1Name, + }) + require.NoError(t, err, "fieldSelector query should succeed") + + // The job might have completed already, but if it exists, it should be the only one + if len(list.Items) > 0 { + require.Len(t, list.Items, 1, "should return at most one job") + require.Equal(t, job1Name, list.Items[0].GetName(), "should return the first job") + } + }) + + t.Run("should return empty when fieldSelector does not match any job", func(t *testing.T) { + list, err := helper.Jobs.Resource.List(ctx, metav1.ListOptions{ + FieldSelector: "metadata.name=non-existent-job", + }) + require.NoError(t, err, "fieldSelector query should succeed even with no matches") + require.Empty(t, list.Items, "should return empty list when no jobs match") + }) + + t.Run("listing without fieldSelector should work", func(t *testing.T) { + list, err := helper.Jobs.Resource.List(ctx, metav1.ListOptions{}) + require.NoError(t, err, "should be able to list without fieldSelector") + // Jobs may have completed, so we don't assert on count, just that the query works + t.Logf("Found %d active jobs without filter", len(list.Items)) + }) +} From 9bec62a080303d7e73d1659575c1a6ff45ddee70 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 11 Dec 2025 13:37:45 +0300 Subject: [PATCH 012/297] Live: simplify dependencies (#115130) --- apps/iam/go.sum | 14 +-- go.mod | 7 +- go.sum | 14 +-- go.work.sum | 8 ++ pkg/api/dashboard_test.go | 10 +-- pkg/api/datasources_test.go | 4 +- pkg/api/dtos/live.go | 11 --- pkg/server/wire_gen.go | 16 ++-- pkg/services/live/database/storage.go | 48 ---------- pkg/services/live/database/tests/setup.go | 18 ---- .../live/database/tests/storage_test.go | 67 -------------- pkg/services/live/features/broadcast.go | 70 --------------- pkg/services/live/features/broadcast_mock.go | 66 -------------- pkg/services/live/features/broadcast_test.go | 87 ------------------- pkg/services/live/features/dashboard.go | 4 +- pkg/services/live/features/plugin.go | 2 + pkg/services/live/live.go | 76 ++++------------ pkg/services/live/live_test.go | 24 ++--- pkg/services/live/livecontext/context.go | 19 ---- pkg/services/live/model/model.go | 20 +---- .../live/pipeline/data_output_builtin.go | 6 +- .../live/pipeline/subscribe_builtin.go | 5 +- .../live/pipeline/subscribe_managed_stream.go | 6 +- .../live/pipeline/subscribe_multiple.go | 1 - pkg/services/live/pushws/push_pipeline.go | 5 +- pkg/services/live/pushws/push_stream.go | 7 +- 26 files changed, 93 insertions(+), 522 deletions(-) delete mode 100644 pkg/api/dtos/live.go delete mode 100644 pkg/services/live/database/storage.go delete mode 100644 pkg/services/live/database/tests/setup.go delete mode 100644 pkg/services/live/database/tests/storage_test.go delete mode 100644 pkg/services/live/features/broadcast.go delete mode 100644 pkg/services/live/features/broadcast_mock.go delete mode 100644 pkg/services/live/features/broadcast_test.go diff --git a/apps/iam/go.sum b/apps/iam/go.sum index 0b22bce3774..4e1eb9d56c7 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -377,10 +377,10 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/centrifugal/centrifuge v0.37.2 h1:rerQNvDfYN2FZEkVtb/hvGV7SIrJfEQrKF3MaE8GDlo= -github.com/centrifugal/centrifuge v0.37.2/go.mod h1:aj4iRJGhzi3SlL8iUtVezxway1Xf8g+hmNQkLLO7sS8= -github.com/centrifugal/protocol v0.16.2 h1:KoIHgDeX1fFxyxQoKW+6E8ZTCf5mwGm8JyGoJ5NBMbQ= -github.com/centrifugal/protocol v0.16.2/go.mod h1:Q7OpS/8HMXDnL7f9DpNx24IhG96MP88WPpVTTCdrokI= +github.com/centrifugal/centrifuge v0.38.0 h1:UJTowwc5lSwnpvd3vbrTseODbU7osSggN67RTrJ8EfQ= +github.com/centrifugal/centrifuge v0.38.0/go.mod h1:rcZLARnO5GXOeE9qG7iIPMvERxESespqkSX4cGLCAzo= +github.com/centrifugal/protocol v0.17.0 h1:hD0WczyiG7zrVJcgkQsd5/nhfFXt0Y04SJHV2Z7B1rg= +github.com/centrifugal/protocol v0.17.0/go.mod h1:9MdiYyjw5Bw1+d5Sp4Y0NK+qiuTNyd88nrHJsUUh8k4= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -1376,11 +1376,13 @@ github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9p github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0= github.com/puzpuzpuz/xsync/v4 v4.2.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= +github.com/quagmt/udecimal v1.9.0 h1:TLuZiFeg0HhS6X8VDa78Y6XTaitZZfh+z5q4SXMzpDQ= +github.com/quagmt/udecimal v1.9.0/go.mod h1:ScmJ/xTGZcEoYiyMMzgDLn79PEJHcMBiJ4NNRT3FirA= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE= github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= -github.com/redis/rueidis v1.0.64 h1:XqgbueDuNV3qFdVdQwAHJl1uNt90zUuAJuzqjH4cw6Y= -github.com/redis/rueidis v1.0.64/go.mod h1:Lkhr2QTgcoYBhxARU7kJRO8SyVlgUuEkcJO1Y8MCluA= +github.com/redis/rueidis v1.0.68 h1:gept0E45JGxVigWb3zoWHvxEc4IOC7kc4V/4XvN8eG8= +github.com/redis/rueidis v1.0.68/go.mod h1:Lkhr2QTgcoYBhxARU7kJRO8SyVlgUuEkcJO1Y8MCluA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= diff --git a/go.mod b/go.mod index 40e3288158d..1ca49313cab 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( github.com/blugelabs/bluge_segment_api v0.2.0 // @grafana/grafana-backend-group github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // @grafana/grafana-backend-group github.com/bwmarrin/snowflake v0.3.0 // @grafana/grafana-app-platform-squad - github.com/centrifugal/centrifuge v0.37.2 // @grafana/grafana-app-platform-squad + github.com/centrifugal/centrifuge v0.38.0 // @grafana/grafana-app-platform-squad github.com/crewjam/saml v0.4.14 // @grafana/identity-access-team github.com/dgraph-io/badger/v4 v4.7.0 // @grafana/grafana-search-and-storage github.com/dlmiddlecote/sqlstats v1.0.2 // @grafana/grafana-backend-group @@ -386,7 +386,7 @@ require ( github.com/caio/go-tdigest v3.1.0+incompatible // indirect github.com/cenkalti/backoff/v4 v4.3.0 // @grafana/alerting-backend github.com/cenkalti/backoff/v5 v5.0.3 // indirect - github.com/centrifugal/protocol v0.16.2 // indirect + github.com/centrifugal/protocol v0.17.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect @@ -562,7 +562,7 @@ require ( github.com/prometheus/procfs v0.16.1 // indirect github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect - github.com/redis/rueidis v1.0.64 // indirect + github.com/redis/rueidis v1.0.68 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect @@ -687,6 +687,7 @@ require ( github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/quagmt/udecimal v1.9.0 // indirect github.com/shirou/gopsutil/v4 v4.25.3 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.8.0 // indirect diff --git a/go.sum b/go.sum index f4f91335d2b..58adec21785 100644 --- a/go.sum +++ b/go.sum @@ -1006,10 +1006,10 @@ github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F9 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= -github.com/centrifugal/centrifuge v0.37.2 h1:rerQNvDfYN2FZEkVtb/hvGV7SIrJfEQrKF3MaE8GDlo= -github.com/centrifugal/centrifuge v0.37.2/go.mod h1:aj4iRJGhzi3SlL8iUtVezxway1Xf8g+hmNQkLLO7sS8= -github.com/centrifugal/protocol v0.16.2 h1:KoIHgDeX1fFxyxQoKW+6E8ZTCf5mwGm8JyGoJ5NBMbQ= -github.com/centrifugal/protocol v0.16.2/go.mod h1:Q7OpS/8HMXDnL7f9DpNx24IhG96MP88WPpVTTCdrokI= +github.com/centrifugal/centrifuge v0.38.0 h1:UJTowwc5lSwnpvd3vbrTseODbU7osSggN67RTrJ8EfQ= +github.com/centrifugal/centrifuge v0.38.0/go.mod h1:rcZLARnO5GXOeE9qG7iIPMvERxESespqkSX4cGLCAzo= +github.com/centrifugal/protocol v0.17.0 h1:hD0WczyiG7zrVJcgkQsd5/nhfFXt0Y04SJHV2Z7B1rg= +github.com/centrifugal/protocol v0.17.0/go.mod h1:9MdiYyjw5Bw1+d5Sp4Y0NK+qiuTNyd88nrHJsUUh8k4= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -2334,11 +2334,13 @@ github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9p github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0= github.com/puzpuzpuz/xsync/v4 v4.2.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= +github.com/quagmt/udecimal v1.9.0 h1:TLuZiFeg0HhS6X8VDa78Y6XTaitZZfh+z5q4SXMzpDQ= +github.com/quagmt/udecimal v1.9.0/go.mod h1:ScmJ/xTGZcEoYiyMMzgDLn79PEJHcMBiJ4NNRT3FirA= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.14.0 h1:u4tNCjXOyzfgeLN+vAZaW1xUooqWDqVEsZN0U01jfAE= github.com/redis/go-redis/v9 v9.14.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= -github.com/redis/rueidis v1.0.64 h1:XqgbueDuNV3qFdVdQwAHJl1uNt90zUuAJuzqjH4cw6Y= -github.com/redis/rueidis v1.0.64/go.mod h1:Lkhr2QTgcoYBhxARU7kJRO8SyVlgUuEkcJO1Y8MCluA= +github.com/redis/rueidis v1.0.68 h1:gept0E45JGxVigWb3zoWHvxEc4IOC7kc4V/4XvN8eG8= +github.com/redis/rueidis v1.0.68/go.mod h1:Lkhr2QTgcoYBhxARU7kJRO8SyVlgUuEkcJO1Y8MCluA= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= diff --git a/go.work.sum b/go.work.sum index 72f38753b02..3462a071ffd 100644 --- a/go.work.sum +++ b/go.work.sum @@ -708,6 +708,8 @@ github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 h1:R/ZjJpjQKsZ6L/+Gf9WHbt31GG8NMVcpRqUE+1mMIyo= +github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731/go.mod h1:M9R1FoZ3y//hwwnJtO51ypFGwm8ZfpxPT/ZLtO1mcgQ= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= @@ -1330,6 +1332,7 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgc github.com/pkg/sftp v1.13.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs= github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA= github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= +github.com/planetscale/vtprotobuf v0.6.0/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= @@ -1397,6 +1400,7 @@ github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtm github.com/schollz/progressbar/v3 v3.14.6 h1:GyjwcWBAf+GFDMLziwerKvpuS7ZF+mNTAXIB2aspiZs= github.com/schollz/progressbar/v3 v3.14.6/go.mod h1:Nrzpuw3Nl0srLY0VlTvC4V6RL50pcEymjy6qyJAaLa0= github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= +github.com/segmentio/asm v1.1.4/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= github.com/segmentio/parquet-go v0.0.0-20220811205829-7efc157d28af/go.mod h1:PxYdAI6cGd+s1j4hZDQbz3VFgobF5fDA0weLeNWKTE4= @@ -1935,6 +1939,7 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= @@ -2001,6 +2006,7 @@ golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= @@ -2077,6 +2083,7 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go. google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822 h1:zWFRixYR5QlotL+Uv3YfsPRENIrQFXiGs+iwqel6fOQ= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= @@ -2107,6 +2114,7 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 6c140667a37..e33f0f5fdaa 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -111,17 +111,15 @@ func TestGetHomeDashboard(t *testing.T) { } } -func newTestLive(t *testing.T, store db.DB) *live.GrafanaLive { +func newTestLive(t *testing.T) *live.GrafanaLive { features := featuremgmt.WithFeatures() cfg := setting.NewCfg() cfg.AppURL = "http://localhost:3000/" gLive, err := live.ProvideService(nil, cfg, routing.NewRouteRegister(), nil, nil, nil, nil, - store, nil, &usagestats.UsageStatsMock{T: t}, - nil, features, acimpl.ProvideAccessControl(features), &dashboards.FakeDashboardService{}, nil, nil) @@ -751,7 +749,7 @@ func TestIntegrationDashboardAPIEndpoint(t *testing.T) { hs := HTTPServer{ Cfg: cfg, ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()), - Live: newTestLive(t, db.InitTestDB(t)), + Live: newTestLive(t), QuotaService: quotatest.New(false, nil), LibraryElementService: &libraryelementsfake.LibraryElementService{}, DashboardService: dashboardService, @@ -1003,7 +1001,7 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s hs := HTTPServer{ Cfg: cfg, ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()), - Live: newTestLive(t, db.InitTestDB(t)), + Live: newTestLive(t), QuotaService: quotatest.New(false, nil), pluginStore: &pluginstore.FakePluginStore{}, LibraryElementService: &libraryelementsfake.LibraryElementService{}, @@ -1043,7 +1041,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout hs := HTTPServer{ Cfg: cfg, ProvisioningService: provisioning.NewProvisioningServiceMock(context.Background()), - Live: newTestLive(t, db.InitTestDB(t)), + Live: newTestLive(t), QuotaService: quotatest.New(false, nil), LibraryElementService: &libraryelementsfake.LibraryElementService{}, DashboardService: mock, diff --git a/pkg/api/datasources_test.go b/pkg/api/datasources_test.go index e2f951b0a37..dd0b39e13a7 100644 --- a/pkg/api/datasources_test.go +++ b/pkg/api/datasources_test.go @@ -343,7 +343,7 @@ func TestUpdateDataSourceByID_DataSourceNameExists(t *testing.T) { Cfg: setting.NewCfg(), AccessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), accesscontrolService: actest.FakeService{}, - Live: newTestLive(t, nil), + Live: newTestLive(t), } sc := setupScenarioContext(t, "/api/datasources/1") @@ -450,7 +450,7 @@ func TestAPI_datasources_AccessControl(t *testing.T) { hs.Cfg = setting.NewCfg() hs.DataSourcesService = &dataSourcesServiceMock{expectedDatasource: &datasources.DataSource{}} hs.accesscontrolService = actest.FakeService{} - hs.Live = newTestLive(t, hs.SQLStore) + hs.Live = newTestLive(t) hs.promRegister, hs.dsConfigHandlerRequestsDuration = setupDsConfigHandlerMetrics() }) diff --git a/pkg/api/dtos/live.go b/pkg/api/dtos/live.go deleted file mode 100644 index 6b524584187..00000000000 --- a/pkg/api/dtos/live.go +++ /dev/null @@ -1,11 +0,0 @@ -package dtos - -import "encoding/json" - -type LivePublishCmd struct { - Channel string `json:"channel"` - Data json.RawMessage `json:"data,omitempty"` -} - -type LivePublishResponse struct { -} diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 1406e6fdbf8..f31f8c18948 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -672,10 +672,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api starService := starimpl.ProvideService(sqlStore) searchSearchService := search2.ProvideService(cfg, sqlStore, starService, dashboardService, folderimplService, featureToggles, sortService) plugincontextProvider := plugincontext.ProvideService(cfg, cacheService, pluginstoreService, cacheServiceImpl, service15, service13, requestConfigProvider) - qsDatasourceClientBuilder := dsquerierclient.NewNullQSDatasourceClientBuilder() - exprService := expr.ProvideService(cfg, middlewareHandler, plugincontextProvider, featureToggles, registerer, tracingService, qsDatasourceClientBuilder) - queryServiceImpl := query.ProvideService(cfg, cacheServiceImpl, exprService, ossDataSourceRequestValidator, middlewareHandler, plugincontextProvider, qsDatasourceClientBuilder) - grafanaLive, err := live.ProvideService(plugincontextProvider, cfg, routeRegisterImpl, pluginstoreService, middlewareHandler, cacheService, cacheServiceImpl, sqlStore, secretsService, usageStats, queryServiceImpl, featureToggles, accessControl, dashboardService, orgService, eventualRestConfigProvider) + grafanaLive, err := live.ProvideService(plugincontextProvider, cfg, routeRegisterImpl, pluginstoreService, middlewareHandler, cacheService, cacheServiceImpl, secretsService, usageStats, featureToggles, accessControl, dashboardService, orgService, eventualRestConfigProvider) if err != nil { return nil, err } @@ -684,6 +681,8 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api authnAuthenticator := authnimpl.ProvideAuthnServiceAuthenticateOnly(authnimplService) contexthandlerContextHandler := contexthandler.ProvideService(cfg, authnAuthenticator, featureToggles) logger := loggermw.Provide(cfg, featureToggles) + qsDatasourceClientBuilder := dsquerierclient.NewNullQSDatasourceClientBuilder() + exprService := expr.ProvideService(cfg, middlewareHandler, plugincontextProvider, featureToggles, registerer, tracingService, qsDatasourceClientBuilder) ngAlert := metrics2.ProvideService() repositoryImpl := annotationsimpl.ProvideService(sqlStore, cfg, featureToggles, tagimplService, tracingService, dBstore, dashboardService, registerer) alertNG, err := ngalert.ProvideService(cfg, featureToggles, cacheServiceImpl, service15, routeRegisterImpl, sqlStore, kvStore, exprService, dataSourceProxyService, quotaService, secretsService, notificationService, ngAlert, folderimplService, accessControl, dashboardService, renderingService, inProcBus, acimplService, repositoryImpl, pluginstoreService, tracingService, dBstore, httpclientProvider, plugincontextProvider, receiverPermissionsService, userService) @@ -708,6 +707,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api } ossSearchUserFilter := filters.ProvideOSSSearchUserFilter() ossService := searchusers.ProvideUsersService(cfg, ossSearchUserFilter, userService) + queryServiceImpl := query.ProvideService(cfg, cacheServiceImpl, exprService, ossDataSourceRequestValidator, middlewareHandler, plugincontextProvider, qsDatasourceClientBuilder) serviceAccountsProxy, err := proxy.ProvideServiceAccountsProxy(cfg, accessControl, acimplService, featureToggles, serviceAccountPermissionsService, serviceAccountsService, routeRegisterImpl) if err != nil { return nil, err @@ -1329,10 +1329,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac starService := starimpl.ProvideService(sqlStore) searchSearchService := search2.ProvideService(cfg, sqlStore, starService, dashboardService, folderimplService, featureToggles, sortService) plugincontextProvider := plugincontext.ProvideService(cfg, cacheService, pluginstoreService, cacheServiceImpl, service15, service13, requestConfigProvider) - qsDatasourceClientBuilder := dsquerierclient.NewNullQSDatasourceClientBuilder() - exprService := expr.ProvideService(cfg, middlewareHandler, plugincontextProvider, featureToggles, registerer, tracingService, qsDatasourceClientBuilder) - queryServiceImpl := query.ProvideService(cfg, cacheServiceImpl, exprService, ossDataSourceRequestValidator, middlewareHandler, plugincontextProvider, qsDatasourceClientBuilder) - grafanaLive, err := live.ProvideService(plugincontextProvider, cfg, routeRegisterImpl, pluginstoreService, middlewareHandler, cacheService, cacheServiceImpl, sqlStore, secretsService, usageStats, queryServiceImpl, featureToggles, accessControl, dashboardService, orgService, eventualRestConfigProvider) + grafanaLive, err := live.ProvideService(plugincontextProvider, cfg, routeRegisterImpl, pluginstoreService, middlewareHandler, cacheService, cacheServiceImpl, secretsService, usageStats, featureToggles, accessControl, dashboardService, orgService, eventualRestConfigProvider) if err != nil { return nil, err } @@ -1341,6 +1338,8 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac authnAuthenticator := authnimpl.ProvideAuthnServiceAuthenticateOnly(authnimplService) contexthandlerContextHandler := contexthandler.ProvideService(cfg, authnAuthenticator, featureToggles) logger := loggermw.Provide(cfg, featureToggles) + qsDatasourceClientBuilder := dsquerierclient.NewNullQSDatasourceClientBuilder() + exprService := expr.ProvideService(cfg, middlewareHandler, plugincontextProvider, featureToggles, registerer, tracingService, qsDatasourceClientBuilder) notificationServiceMock := notifications.MockNotificationService() ngAlert := metrics2.ProvideServiceForTest() repositoryImpl := annotationsimpl.ProvideService(sqlStore, cfg, featureToggles, tagimplService, tracingService, dBstore, dashboardService, registerer) @@ -1366,6 +1365,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac } ossSearchUserFilter := filters.ProvideOSSSearchUserFilter() ossService := searchusers.ProvideUsersService(cfg, ossSearchUserFilter, userService) + queryServiceImpl := query.ProvideService(cfg, cacheServiceImpl, exprService, ossDataSourceRequestValidator, middlewareHandler, plugincontextProvider, qsDatasourceClientBuilder) serviceAccountsProxy, err := proxy.ProvideServiceAccountsProxy(cfg, accessControl, acimplService, featureToggles, serviceAccountPermissionsService, serviceAccountsService, routeRegisterImpl) if err != nil { return nil, err diff --git a/pkg/services/live/database/storage.go b/pkg/services/live/database/storage.go deleted file mode 100644 index 3736c27d265..00000000000 --- a/pkg/services/live/database/storage.go +++ /dev/null @@ -1,48 +0,0 @@ -package database - -import ( - "fmt" - "time" - - "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/infra/localcache" - "github.com/grafana/grafana/pkg/services/live/model" -) - -type Storage struct { - store db.DB - cache *localcache.CacheService -} - -func NewStorage(store db.DB, cache *localcache.CacheService) *Storage { - return &Storage{store: store, cache: cache} -} - -func getLiveMessageCacheKey(orgID int64, channel string) string { - return fmt.Sprintf("live_message_%d_%s", orgID, channel) -} - -func (s *Storage) SaveLiveMessage(query *model.SaveLiveMessageQuery) error { - // Come back to saving into database after evaluating database structure. - s.cache.Set(getLiveMessageCacheKey(query.OrgID, query.Channel), model.LiveMessage{ - ID: 0, // Not used actually. - OrgID: query.OrgID, - Channel: query.Channel, - Data: query.Data, - Published: time.Now(), - }, 0) - return nil -} - -func (s *Storage) GetLiveMessage(query *model.GetLiveMessageQuery) (model.LiveMessage, bool, error) { - // Come back to saving into database after evaluating database structure. - m, ok := s.cache.Get(getLiveMessageCacheKey(query.OrgID, query.Channel)) - if !ok { - return model.LiveMessage{}, false, nil - } - msg, ok := m.(model.LiveMessage) - if !ok { - return model.LiveMessage{}, false, fmt.Errorf("unexpected live message type in cache: %T", m) - } - return msg, true, nil -} diff --git a/pkg/services/live/database/tests/setup.go b/pkg/services/live/database/tests/setup.go deleted file mode 100644 index 9ebf8f84a26..00000000000 --- a/pkg/services/live/database/tests/setup.go +++ /dev/null @@ -1,18 +0,0 @@ -package tests - -import ( - "testing" - "time" - - "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/infra/localcache" - "github.com/grafana/grafana/pkg/services/live/database" -) - -// SetupTestStorage initializes a storage to used by the integration tests. -// This is required to properly register and execute migrations. -func SetupTestStorage(t *testing.T) *database.Storage { - sqlStore := db.InitTestDB(t) - localCache := localcache.New(time.Hour, time.Hour) - return database.NewStorage(sqlStore, localCache) -} diff --git a/pkg/services/live/database/tests/storage_test.go b/pkg/services/live/database/tests/storage_test.go deleted file mode 100644 index 0c57ec08e50..00000000000 --- a/pkg/services/live/database/tests/storage_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package tests - -import ( - "encoding/json" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/services/live/model" - "github.com/grafana/grafana/pkg/tests/testsuite" - "github.com/grafana/grafana/pkg/util/testutil" -) - -func TestMain(m *testing.M) { - testsuite.Run(m) -} - -func TestIntegrationLiveMessage(t *testing.T) { - testutil.SkipIntegrationTestInShortMode(t) - - storage := SetupTestStorage(t) - - getQuery := &model.GetLiveMessageQuery{ - OrgID: 1, - Channel: "test_channel", - } - _, ok, err := storage.GetLiveMessage(getQuery) - require.NoError(t, err) - require.False(t, ok) - - saveQuery := &model.SaveLiveMessageQuery{ - OrgID: 1, - Channel: "test_channel", - Data: []byte(`{}`), - } - err = storage.SaveLiveMessage(saveQuery) - require.NoError(t, err) - - msg, ok, err := storage.GetLiveMessage(getQuery) - require.NoError(t, err) - require.True(t, ok) - require.Equal(t, int64(1), msg.OrgID) - require.Equal(t, "test_channel", msg.Channel) - require.Equal(t, json.RawMessage(`{}`), msg.Data) - require.NotZero(t, msg.Published) - - // try saving again, should be replaced. - saveQuery2 := &model.SaveLiveMessageQuery{ - OrgID: 1, - Channel: "test_channel", - Data: []byte(`{"input": "hello"}`), - } - err = storage.SaveLiveMessage(saveQuery2) - require.NoError(t, err) - - getQuery2 := &model.GetLiveMessageQuery{ - OrgID: 1, - Channel: "test_channel", - } - msg2, ok, err := storage.GetLiveMessage(getQuery2) - require.NoError(t, err) - require.True(t, ok) - require.Equal(t, int64(1), msg2.OrgID) - require.Equal(t, "test_channel", msg2.Channel) - require.Equal(t, json.RawMessage(`{"input": "hello"}`), msg2.Data) - require.NotZero(t, msg2.Published) -} diff --git a/pkg/services/live/features/broadcast.go b/pkg/services/live/features/broadcast.go deleted file mode 100644 index fc9bbc56f2b..00000000000 --- a/pkg/services/live/features/broadcast.go +++ /dev/null @@ -1,70 +0,0 @@ -package features - -import ( - "context" - - "github.com/grafana/grafana-plugin-sdk-go/backend" - - "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/services/live/model" -) - -var ( - logger = log.New("live.features") // scoped to all features? -) - -//go:generate mockgen -destination=broadcast_mock.go -package=features github.com/grafana/grafana/pkg/services/live/features LiveMessageStore - -type LiveMessageStore interface { - SaveLiveMessage(query *model.SaveLiveMessageQuery) error - GetLiveMessage(query *model.GetLiveMessageQuery) (model.LiveMessage, bool, error) -} - -// BroadcastRunner will simply broadcast all events to `grafana/broadcast/*` channels -// This assumes that data is a JSON object -type BroadcastRunner struct { - liveMessageStore LiveMessageStore -} - -func NewBroadcastRunner(liveMessageStore LiveMessageStore) *BroadcastRunner { - return &BroadcastRunner{liveMessageStore: liveMessageStore} -} - -// GetHandlerForPath called on init -func (b *BroadcastRunner) GetHandlerForPath(_ string) (model.ChannelHandler, error) { - return b, nil // all dashboards share the same handler -} - -// OnSubscribe will let anyone connect to the path -func (b *BroadcastRunner) OnSubscribe(_ context.Context, u identity.Requester, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) { - reply := model.SubscribeReply{ - Presence: true, - JoinLeave: true, - } - query := &model.GetLiveMessageQuery{ - OrgID: u.GetOrgID(), - Channel: e.Channel, - } - msg, ok, err := b.liveMessageStore.GetLiveMessage(query) - if err != nil { - return model.SubscribeReply{}, 0, err - } - if ok { - reply.Data = msg.Data - } - return reply, backend.SubscribeStreamStatusOK, nil -} - -// OnPublish is called when a client wants to broadcast on the websocket -func (b *BroadcastRunner) OnPublish(_ context.Context, u identity.Requester, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) { - query := &model.SaveLiveMessageQuery{ - OrgID: u.GetOrgID(), - Channel: e.Channel, - Data: e.Data, - } - if err := b.liveMessageStore.SaveLiveMessage(query); err != nil { - return model.PublishReply{}, 0, err - } - return model.PublishReply{Data: e.Data}, backend.PublishStreamStatusOK, nil -} diff --git a/pkg/services/live/features/broadcast_mock.go b/pkg/services/live/features/broadcast_mock.go deleted file mode 100644 index 54df3472a98..00000000000 --- a/pkg/services/live/features/broadcast_mock.go +++ /dev/null @@ -1,66 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/grafana/grafana/pkg/services/live/features (interfaces: LiveMessageStore) - -// Package features is a generated GoMock package. -package features - -import ( - reflect "reflect" - - gomock "github.com/golang/mock/gomock" - - model "github.com/grafana/grafana/pkg/services/live/model" -) - -// MockLiveMessageStore is a mock of LiveMessageStore interface. -type MockLiveMessageStore struct { - ctrl *gomock.Controller - recorder *MockLiveMessageStoreMockRecorder -} - -// MockLiveMessageStoreMockRecorder is the mock recorder for MockLiveMessageStore. -type MockLiveMessageStoreMockRecorder struct { - mock *MockLiveMessageStore -} - -// NewMockLiveMessageStore creates a new mock instance. -func NewMockLiveMessageStore(ctrl *gomock.Controller) *MockLiveMessageStore { - mock := &MockLiveMessageStore{ctrl: ctrl} - mock.recorder = &MockLiveMessageStoreMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockLiveMessageStore) EXPECT() *MockLiveMessageStoreMockRecorder { - return m.recorder -} - -// GetLiveMessage mocks base method. -func (m *MockLiveMessageStore) GetLiveMessage(arg0 *model.GetLiveMessageQuery) (model.LiveMessage, bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLiveMessage", arg0) - ret0, _ := ret[0].(model.LiveMessage) - ret1, _ := ret[1].(bool) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// GetLiveMessage indicates an expected call of GetLiveMessage. -func (mr *MockLiveMessageStoreMockRecorder) GetLiveMessage(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLiveMessage", reflect.TypeOf((*MockLiveMessageStore)(nil).GetLiveMessage), arg0) -} - -// SaveLiveMessage mocks base method. -func (m *MockLiveMessageStore) SaveLiveMessage(arg0 *model.SaveLiveMessageQuery) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SaveLiveMessage", arg0) - ret0, _ := ret[0].(error) - return ret0 -} - -// SaveLiveMessage indicates an expected call of SaveLiveMessage. -func (mr *MockLiveMessageStoreMockRecorder) SaveLiveMessage(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveLiveMessage", reflect.TypeOf((*MockLiveMessageStore)(nil).SaveLiveMessage), arg0) -} diff --git a/pkg/services/live/features/broadcast_test.go b/pkg/services/live/features/broadcast_test.go deleted file mode 100644 index 25065087420..00000000000 --- a/pkg/services/live/features/broadcast_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package features - -import ( - "context" - "encoding/json" - "testing" - - "github.com/golang/mock/gomock" - "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/services/live/model" - "github.com/grafana/grafana/pkg/services/user" -) - -func TestNewBroadcastRunner(t *testing.T) { - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - d := NewMockLiveMessageStore(mockCtrl) - br := NewBroadcastRunner(d) - require.NotNil(t, br) -} - -func TestBroadcastRunner_OnSubscribe(t *testing.T) { - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - mockDispatcher := NewMockLiveMessageStore(mockCtrl) - - channel := "stream/channel/test" - data := json.RawMessage(`{}`) - - mockDispatcher.EXPECT().GetLiveMessage(&model.GetLiveMessageQuery{ - OrgID: 1, - Channel: channel, - }).DoAndReturn(func(query *model.GetLiveMessageQuery) (model.LiveMessage, bool, error) { - return model.LiveMessage{ - Data: data, - }, true, nil - }).Times(1) - - br := NewBroadcastRunner(mockDispatcher) - require.NotNil(t, br) - handler, err := br.GetHandlerForPath("test") - require.NoError(t, err) - reply, status, err := handler.OnSubscribe( - context.Background(), - &user.SignedInUser{OrgID: 1, UserID: 2}, - model.SubscribeEvent{Channel: channel, Path: "test"}, - ) - require.NoError(t, err) - require.Equal(t, backend.SubscribeStreamStatusOK, status) - require.Equal(t, data, reply.Data) - require.True(t, reply.Presence) - require.True(t, reply.JoinLeave) - require.False(t, reply.Recover) -} - -func TestBroadcastRunner_OnPublish(t *testing.T) { - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - mockDispatcher := NewMockLiveMessageStore(mockCtrl) - - channel := "stream/channel/test" - data := json.RawMessage(`{}`) - var orgID int64 = 1 - - mockDispatcher.EXPECT().SaveLiveMessage(&model.SaveLiveMessageQuery{ - OrgID: orgID, - Channel: channel, - Data: data, - }).DoAndReturn(func(query *model.SaveLiveMessageQuery) error { - return nil - }).Times(1) - - br := NewBroadcastRunner(mockDispatcher) - require.NotNil(t, br) - handler, err := br.GetHandlerForPath("test") - require.NoError(t, err) - reply, status, err := handler.OnPublish( - context.Background(), - &user.SignedInUser{OrgID: 1, UserID: 2}, - model.PublishEvent{Channel: channel, Path: "test", Data: data}, - ) - require.NoError(t, err) - require.Equal(t, backend.PublishStreamStatusOK, status) - require.Equal(t, data, reply.Data) -} diff --git a/pkg/services/live/features/dashboard.go b/pkg/services/live/features/dashboard.go index cda4893c258..5cf43bafcf7 100644 --- a/pkg/services/live/features/dashboard.go +++ b/pkg/services/live/features/dashboard.go @@ -7,9 +7,8 @@ import ( "strings" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/live/model" @@ -35,7 +34,6 @@ type dashboardEvent struct { type DashboardHandler struct { Publisher model.ChannelPublisher ClientCount model.ChannelClientCount - Store db.DB DashboardService dashboards.DashboardService AccessControl accesscontrol.AccessControl } diff --git a/pkg/services/live/features/plugin.go b/pkg/services/live/features/plugin.go index de7a55ea2ab..1f7d0768078 100644 --- a/pkg/services/live/features/plugin.go +++ b/pkg/services/live/features/plugin.go @@ -5,9 +5,11 @@ import ( "errors" "github.com/centrifugal/centrifuge" + "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/live/model" "github.com/grafana/grafana/pkg/services/live/orgchannel" diff --git a/pkg/services/live/live.go b/pkg/services/live/live.go index cffdaaf0ec6..675782ac830 100644 --- a/pkg/services/live/live.go +++ b/pkg/services/live/live.go @@ -15,7 +15,6 @@ import ( "github.com/centrifugal/centrifuge" "github.com/gobwas/glob" - jsoniter "github.com/json-iterator/go" "github.com/redis/go-redis/v9" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -25,12 +24,9 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/live" - "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" - "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/usagestats" @@ -43,7 +39,6 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/live/database" "github.com/grafana/grafana/pkg/services/live/features" "github.com/grafana/grafana/pkg/services/live/livecontext" "github.com/grafana/grafana/pkg/services/live/liveplugin" @@ -57,7 +52,6 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" - "github.com/grafana/grafana/pkg/services/query" "github.com/grafana/grafana/pkg/services/secrets" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" @@ -80,8 +74,8 @@ type CoreGrafanaScope struct { func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, routeRegister routing.RouteRegister, pluginStore pluginstore.Store, pluginClient plugins.Client, cacheService *localcache.CacheService, - dataSourceCache datasources.CacheService, sqlStore db.DB, secretsService secrets.Service, - usageStatsService usagestats.Service, queryDataService query.Service, toggles featuremgmt.FeatureToggles, + dataSourceCache datasources.CacheService, secretsService secrets.Service, + usageStatsService usagestats.Service, toggles featuremgmt.FeatureToggles, accessControl accesscontrol.AccessControl, dashboardService dashboards.DashboardService, orgService org.Service, configProvider apiserver.RestConfigProvider) (*GrafanaLive, error) { g := &GrafanaLive{ @@ -93,9 +87,7 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r pluginClient: pluginClient, CacheService: cacheService, DataSourceCache: dataSourceCache, - SQLStore: sqlStore, SecretsService: secretsService, - queryDataService: queryDataService, channels: make(map[string]model.ChannelHandler), GrafanaScope: CoreGrafanaScope{ Features: make(map[string]model.ChannelHandlerFactory), @@ -186,14 +178,11 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r dash := &features.DashboardHandler{ Publisher: g.Publish, ClientCount: g.ClientCount, - Store: sqlStore, DashboardService: dashboardService, AccessControl: accessControl, } - g.storage = database.NewStorage(g.SQLStore, g.CacheService) g.GrafanaScope.Dashboards = dash g.GrafanaScope.Features["dashboard"] = dash - g.GrafanaScope.Features["broadcast"] = features.NewBroadcastRunner(g.storage) // Testing watch with just the provisioning support -- this will be removed when it is well validated //nolint:staticcheck // not yet migrated to OpenFeature @@ -388,14 +377,14 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r UserID: strconv.FormatInt(id, 10), } newCtx := centrifuge.SetCredentials(ctx.Req.Context(), cred) - newCtx = livecontext.SetContextSignedUser(newCtx, user) + newCtx = identity.WithRequester(newCtx, user) r := ctx.Req.WithContext(newCtx) wsHandler.ServeHTTP(ctx.Resp, r) } g.pushWebsocketHandler = func(ctx *contextmodel.ReqContext) { user := ctx.SignedInUser - newCtx := livecontext.SetContextSignedUser(ctx.Req.Context(), user) + newCtx := identity.WithRequester(ctx.Req.Context(), user) newCtx = livecontext.SetContextStreamID(newCtx, web.Params(ctx.Req)[":streamId"]) r := ctx.Req.WithContext(newCtx) pushWSHandler.ServeHTTP(ctx.Resp, r) @@ -403,7 +392,7 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r g.pushPipelineWebsocketHandler = func(ctx *contextmodel.ReqContext) { user := ctx.SignedInUser - newCtx := livecontext.SetContextSignedUser(ctx.Req.Context(), user) + newCtx := identity.WithRequester(ctx.Req.Context(), user) newCtx = livecontext.SetContextChannelID(newCtx, web.Params(ctx.Req)["*"]) r := ctx.Req.WithContext(newCtx) pushPipelineWSHandler.ServeHTTP(ctx.Resp, r) @@ -475,14 +464,12 @@ type GrafanaLive struct { RouteRegister routing.RouteRegister CacheService *localcache.CacheService DataSourceCache datasources.CacheService - SQLStore db.DB SecretsService secrets.Service pluginStore pluginstore.Store pluginClient plugins.Client - queryDataService query.Service orgService org.Service - keyPrefix string + keyPrefix string // HA prefix for grafana cloud (since the org is always 1) node *centrifuge.Node surveyCaller *survey.Caller @@ -505,7 +492,6 @@ type GrafanaLive struct { contextGetter *liveplugin.ContextGetter runStreamManager *runstream.Manager - storage *database.Storage usageStatsService usagestats.Service usageStats usageStats @@ -673,18 +659,13 @@ func (g *GrafanaLive) HandleDatasourceUpdate(orgID int64, dsUID string) { } } -// Use a configuration that's compatible with the standard library -// to minimize the risk of introducing bugs. This will make sure -// that map keys is ordered. -var jsonStd = jsoniter.ConfigCompatibleWithStandardLibrary - func (g *GrafanaLive) handleOnRPC(clientContextWithSpan context.Context, client *centrifuge.Client, e centrifuge.RPCEvent) (centrifuge.RPCReply, error) { logger.Debug("Client calls RPC", "user", client.UserID(), "client", client.ID(), "method", e.Method) if e.Method != "grafana.query" { return centrifuge.RPCReply{}, centrifuge.ErrorMethodNotFound } - user, ok := livecontext.GetContextSignedUser(clientContextWithSpan) - if !ok { + user, err := identity.GetRequester(clientContextWithSpan) + if err != nil { logger.Error("No user found in context", "user", client.UserID(), "client", client.ID(), "method", e.Method) return centrifuge.RPCReply{}, centrifuge.ErrorInternal } @@ -694,38 +675,15 @@ func (g *GrafanaLive) handleOnRPC(clientContextWithSpan context.Context, client return centrifuge.RPCReply{}, centrifuge.ErrorExpired } - var req dtos.MetricRequest - err := json.Unmarshal(e.Data, &req) - if err != nil { - return centrifuge.RPCReply{}, centrifuge.ErrorBadRequest - } - resp, err := g.queryDataService.QueryData(clientContextWithSpan, user, false, req) - if err != nil { - logger.Error("Error query data", "user", client.UserID(), "client", client.ID(), "method", e.Method, "error", err) - if errors.Is(err, datasources.ErrDataSourceAccessDenied) { - return centrifuge.RPCReply{}, ¢rifuge.Error{Code: uint32(http.StatusForbidden), Message: http.StatusText(http.StatusForbidden)} - } - var gfErr errutil.Error - if errors.As(err, &gfErr) && gfErr.Reason.Status() == errutil.StatusBadRequest { - return centrifuge.RPCReply{}, ¢rifuge.Error{Code: uint32(http.StatusBadRequest), Message: http.StatusText(http.StatusBadRequest)} - } - return centrifuge.RPCReply{}, centrifuge.ErrorInternal - } - data, err := jsonStd.Marshal(resp) - if err != nil { - logger.Error("Error marshaling query response", "user", client.UserID(), "client", client.ID(), "method", e.Method, "error", err) - return centrifuge.RPCReply{}, centrifuge.ErrorInternal - } - return centrifuge.RPCReply{ - Data: data, - }, nil + // RPC events not available + return centrifuge.RPCReply{}, centrifuge.ErrorNotAvailable } func (g *GrafanaLive) handleOnSubscribe(clientContextWithSpan context.Context, client *centrifuge.Client, e centrifuge.SubscribeEvent) (centrifuge.SubscribeReply, error) { logger.Debug("Client wants to subscribe", "user", client.UserID(), "client", client.ID(), "channel", e.Channel) - user, ok := livecontext.GetContextSignedUser(clientContextWithSpan) - if !ok { + user, err := identity.GetRequester(clientContextWithSpan) + if err != nil { logger.Error("No user found in context", "user", client.UserID(), "client", client.ID(), "channel", e.Channel) return centrifuge.SubscribeReply{}, centrifuge.ErrorInternal } @@ -830,8 +788,8 @@ func (g *GrafanaLive) handleOnSubscribe(clientContextWithSpan context.Context, c func (g *GrafanaLive) handleOnPublish(clientCtxWithSpan context.Context, client *centrifuge.Client, e centrifuge.PublishEvent) (centrifuge.PublishReply, error) { logger.Debug("Client wants to publish", "user", client.UserID(), "client", client.ID(), "channel", e.Channel) - user, ok := livecontext.GetContextSignedUser(clientCtxWithSpan) - if !ok { + user, err := identity.GetRequester(clientCtxWithSpan) + if err != nil { logger.Error("No user found in context", "user", client.UserID(), "client", client.ID(), "channel", e.Channel) return centrifuge.PublishReply{}, centrifuge.ErrorInternal } @@ -1083,7 +1041,7 @@ func (g *GrafanaLive) ClientCount(orgID int64, channel string) (int, error) { } func (g *GrafanaLive) HandleHTTPPublish(ctx *contextmodel.ReqContext) response.Response { - cmd := dtos.LivePublishCmd{} + cmd := model.LivePublishCmd{} if err := web.Bind(ctx.Req, &cmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } @@ -1122,7 +1080,7 @@ func (g *GrafanaLive) HandleHTTPPublish(ctx *contextmodel.ReqContext) response.R logger.Error("Error processing input", "user", user, "channel", channel, "error", err) return response.Error(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), nil) } - return response.JSON(http.StatusOK, dtos.LivePublishResponse{}) + return response.JSON(http.StatusOK, model.LivePublishResponse{}) } } @@ -1150,7 +1108,7 @@ func (g *GrafanaLive) HandleHTTPPublish(ctx *contextmodel.ReqContext) response.R } } logger.Debug("Publication successful", "identity", ctx.GetID(), "channel", cmd.Channel) - return response.JSON(http.StatusOK, dtos.LivePublishResponse{}) + return response.JSON(http.StatusOK, model.LivePublishResponse{}) } type streamChannelListResponse struct { diff --git a/pkg/services/live/live_test.go b/pkg/services/live/live_test.go index 00ce4bee3f4..d3dcf378521 100644 --- a/pkg/services/live/live_test.go +++ b/pkg/services/live/live_test.go @@ -11,20 +11,17 @@ import ( "testing" "time" + "github.com/centrifugal/centrifuge" "github.com/go-jose/go-jose/v4" "github.com/go-jose/go-jose/v4/jwt" "github.com/stretchr/testify/require" - "github.com/centrifugal/centrifuge" - "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/live/livecontext" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tests/testsuite" "github.com/grafana/grafana/pkg/util/testutil" @@ -245,7 +242,7 @@ func Test_handleOnPublish_IDTokenExpiration(t *testing.T) { t.Run("expired token", func(t *testing.T) { expiration := time.Now().Add(-time.Hour) token := createToken(t, &expiration) - ctx := livecontext.SetContextSignedUser(context.Background(), &identity.StaticRequester{IDToken: token}) + ctx := identity.WithRequester(context.Background(), &identity.StaticRequester{IDToken: token}) reply, err := g.handleOnPublish(ctx, client, centrifuge.PublishEvent{ Channel: "test", Data: []byte("test"), @@ -257,7 +254,7 @@ func Test_handleOnPublish_IDTokenExpiration(t *testing.T) { t.Run("unexpired token", func(t *testing.T) { expiration := time.Now().Add(time.Hour) token := createToken(t, &expiration) - ctx := livecontext.SetContextSignedUser(context.Background(), &identity.StaticRequester{IDToken: token}) + ctx := identity.WithRequester(context.Background(), &identity.StaticRequester{IDToken: token}) reply, err := g.handleOnPublish(ctx, client, centrifuge.PublishEvent{ Channel: "test", Data: []byte("test"), @@ -280,7 +277,7 @@ func Test_handleOnRPC_IDTokenExpiration(t *testing.T) { t.Run("expired token", func(t *testing.T) { expiration := time.Now().Add(-time.Hour) token := createToken(t, &expiration) - ctx := livecontext.SetContextSignedUser(context.Background(), &identity.StaticRequester{IDToken: token}) + ctx := identity.WithRequester(context.Background(), &identity.StaticRequester{IDToken: token}) reply, err := g.handleOnRPC(ctx, client, centrifuge.RPCEvent{ Method: "grafana.query", Data: []byte("test"), @@ -292,7 +289,7 @@ func Test_handleOnRPC_IDTokenExpiration(t *testing.T) { t.Run("unexpired token", func(t *testing.T) { expiration := time.Now().Add(time.Hour) token := createToken(t, &expiration) - ctx := livecontext.SetContextSignedUser(context.Background(), &identity.StaticRequester{IDToken: token}) + ctx := identity.WithRequester(context.Background(), &identity.StaticRequester{IDToken: token}) reply, err := g.handleOnRPC(ctx, client, centrifuge.RPCEvent{ Method: "grafana.query", Data: []byte("test"), @@ -315,7 +312,7 @@ func Test_handleOnSubscribe_IDTokenExpiration(t *testing.T) { t.Run("expired token", func(t *testing.T) { expiration := time.Now().Add(-time.Hour) token := createToken(t, &expiration) - ctx := livecontext.SetContextSignedUser(context.Background(), &identity.StaticRequester{IDToken: token}) + ctx := identity.WithRequester(context.Background(), &identity.StaticRequester{IDToken: token}) reply, err := g.handleOnSubscribe(ctx, client, centrifuge.SubscribeEvent{ Channel: "test", }) @@ -326,7 +323,7 @@ func Test_handleOnSubscribe_IDTokenExpiration(t *testing.T) { t.Run("unexpired token", func(t *testing.T) { expiration := time.Now().Add(time.Hour) token := createToken(t, &expiration) - ctx := livecontext.SetContextSignedUser(context.Background(), &identity.StaticRequester{IDToken: token}) + ctx := identity.WithRequester(context.Background(), &identity.StaticRequester{IDToken: token}) reply, err := g.handleOnSubscribe(ctx, client, centrifuge.SubscribeEvent{ Channel: "test", }) @@ -347,10 +344,8 @@ func setupLiveService(cfg *setting.Cfg, t *testing.T) (*GrafanaLive, error) { cfg, routing.NewRouteRegister(), nil, nil, nil, nil, - db.InitTestDB(t), nil, &usagestats.UsageStatsMock{T: t}, - nil, featuremgmt.WithFeatures(), acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), &dashboards.FakeDashboardService{}, @@ -361,7 +356,12 @@ type dummyTransport struct { name string } +var ( + _ centrifuge.Transport = (*dummyTransport)(nil) +) + func (t *dummyTransport) Name() string { return t.name } +func (t *dummyTransport) AcceptProtocol() string { return "" } func (t *dummyTransport) Protocol() centrifuge.ProtocolType { return centrifuge.ProtocolTypeJSON } func (t *dummyTransport) ProtocolVersion() centrifuge.ProtocolVersion { return centrifuge.ProtocolVersion2 diff --git a/pkg/services/live/livecontext/context.go b/pkg/services/live/livecontext/context.go index b7c18394cab..3184dc22a93 100644 --- a/pkg/services/live/livecontext/context.go +++ b/pkg/services/live/livecontext/context.go @@ -2,27 +2,8 @@ package livecontext import ( "context" - - "github.com/grafana/grafana/pkg/apimachinery/identity" ) -type signedUserContextKeyType int - -var signedUserContextKey signedUserContextKeyType - -func SetContextSignedUser(ctx context.Context, user identity.Requester) context.Context { - ctx = context.WithValue(ctx, signedUserContextKey, user) - return ctx -} - -func GetContextSignedUser(ctx context.Context) (identity.Requester, bool) { - if val := ctx.Value(signedUserContextKey); val != nil { - user, ok := val.(identity.Requester) - return user, ok - } - return nil, false -} - type streamIDContextKey struct{} func SetContextStreamID(ctx context.Context, streamID string) context.Context { diff --git a/pkg/services/live/model/model.go b/pkg/services/live/model/model.go index 03e7ddcf4eb..1f1c782c914 100644 --- a/pkg/services/live/model/model.go +++ b/pkg/services/live/model/model.go @@ -67,21 +67,9 @@ type ChannelHandlerFactory interface { GetHandlerForPath(path string) (ChannelHandler, error) } -type LiveMessage struct { - ID int64 `xorm:"pk autoincr 'id'"` - OrgID int64 `xorm:"org_id"` - Channel string - Data json.RawMessage - Published time.Time +type LivePublishCmd struct { + Channel string `json:"channel"` + Data json.RawMessage `json:"data,omitempty"` } -type SaveLiveMessageQuery struct { - OrgID int64 `xorm:"org_id"` - Channel string - Data json.RawMessage -} - -type GetLiveMessageQuery struct { - OrgID int64 `xorm:"org_id"` - Channel string -} +type LivePublishResponse struct{} diff --git a/pkg/services/live/pipeline/data_output_builtin.go b/pkg/services/live/pipeline/data_output_builtin.go index 1a4ab62f941..a3c99a9f7d2 100644 --- a/pkg/services/live/pipeline/data_output_builtin.go +++ b/pkg/services/live/pipeline/data_output_builtin.go @@ -6,7 +6,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana/pkg/services/live/livecontext" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/live/model" ) @@ -25,8 +25,8 @@ func (s *BuiltinDataOutput) Type() string { } func (s *BuiltinDataOutput) OutputData(ctx context.Context, vars Vars, data []byte) ([]*ChannelData, error) { - u, ok := livecontext.GetContextSignedUser(ctx) - if !ok { + u, err := identity.GetRequester(ctx) + if err != nil { return nil, errors.New("user not found in context") } handler, _, err := s.channelHandlerGetter.GetChannelHandler(ctx, u, vars.Channel) diff --git a/pkg/services/live/pipeline/subscribe_builtin.go b/pkg/services/live/pipeline/subscribe_builtin.go index e9888c59305..20c2660426c 100644 --- a/pkg/services/live/pipeline/subscribe_builtin.go +++ b/pkg/services/live/pipeline/subscribe_builtin.go @@ -7,7 +7,6 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/live" "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/services/live/livecontext" "github.com/grafana/grafana/pkg/services/live/model" ) @@ -30,8 +29,8 @@ func (s *BuiltinSubscriber) Type() string { } func (s *BuiltinSubscriber) Subscribe(ctx context.Context, vars Vars, data []byte) (model.SubscribeReply, backend.SubscribeStreamStatus, error) { - u, ok := livecontext.GetContextSignedUser(ctx) - if !ok { + u, err := identity.GetRequester(ctx) + if err != nil { return model.SubscribeReply{}, backend.SubscribeStreamStatusPermissionDenied, nil } handler, _, err := s.channelHandlerGetter.GetChannelHandler(ctx, u, vars.Channel) diff --git a/pkg/services/live/pipeline/subscribe_managed_stream.go b/pkg/services/live/pipeline/subscribe_managed_stream.go index 4f668469995..ce26d0d1a30 100644 --- a/pkg/services/live/pipeline/subscribe_managed_stream.go +++ b/pkg/services/live/pipeline/subscribe_managed_stream.go @@ -5,7 +5,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana/pkg/services/live/livecontext" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/live/managedstream" "github.com/grafana/grafana/pkg/services/live/model" ) @@ -30,8 +30,8 @@ func (s *ManagedStreamSubscriber) Subscribe(ctx context.Context, vars Vars, _ [] logger.Error("Error getting managed stream", "error", err) return model.SubscribeReply{}, 0, err } - u, ok := livecontext.GetContextSignedUser(ctx) - if !ok { + u, err := identity.GetRequester(ctx) + if err != nil { return model.SubscribeReply{}, backend.SubscribeStreamStatusPermissionDenied, nil } return stream.OnSubscribe(ctx, u, model.SubscribeEvent{ diff --git a/pkg/services/live/pipeline/subscribe_multiple.go b/pkg/services/live/pipeline/subscribe_multiple.go index 9b7b6789236..a7a6148a99d 100644 --- a/pkg/services/live/pipeline/subscribe_multiple.go +++ b/pkg/services/live/pipeline/subscribe_multiple.go @@ -4,7 +4,6 @@ import ( "context" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana/pkg/services/live/model" ) diff --git a/pkg/services/live/pushws/push_pipeline.go b/pkg/services/live/pushws/push_pipeline.go index 0005318bebd..a455e26ba01 100644 --- a/pkg/services/live/pushws/push_pipeline.go +++ b/pkg/services/live/pushws/push_pipeline.go @@ -5,6 +5,7 @@ import ( "github.com/gorilla/websocket" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/live/convert" "github.com/grafana/grafana/pkg/services/live/livecontext" "github.com/grafana/grafana/pkg/services/live/pipeline" @@ -44,8 +45,8 @@ func (s *PipelinePushHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) return } - user, ok := livecontext.GetContextSignedUser(r.Context()) - if !ok { + user, err := identity.GetRequester(r.Context()) + if err != nil { logger.Error("No user found in context") rw.WriteHeader(http.StatusInternalServerError) return diff --git a/pkg/services/live/pushws/push_stream.go b/pkg/services/live/pushws/push_stream.go index 649a97d9f34..fafcd79f3f0 100644 --- a/pkg/services/live/pushws/push_stream.go +++ b/pkg/services/live/pushws/push_stream.go @@ -5,8 +5,9 @@ import ( "time" "github.com/gorilla/websocket" - liveDto "github.com/grafana/grafana-plugin-sdk-go/live" + liveDto "github.com/grafana/grafana-plugin-sdk-go/live" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/live/convert" "github.com/grafana/grafana/pkg/services/live/livecontext" "github.com/grafana/grafana/pkg/services/live/managedstream" @@ -47,8 +48,8 @@ func (s *Handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { return } - user, ok := livecontext.GetContextSignedUser(r.Context()) - if !ok { + user, err := identity.GetRequester(r.Context()) + if err != nil { logger.Error("No user found in context") rw.WriteHeader(http.StatusInternalServerError) return From d568798c6466d1e27f0c907f5ea8fa3f52edd4fe Mon Sep 17 00:00:00 2001 From: Lauren Date: Thu, 11 Dec 2025 11:01:53 +0000 Subject: [PATCH 013/297] Alerting: Improve instance count display (#114997) * Update button text to Show All if filters are enabled * Show state in text if filters enabled * resolve PR comments --- .../rules/RuleDetailsMatchingInstances.tsx | 40 ++++++++++++++----- public/locales/en-US/grafana.json | 6 ++- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx b/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx index 7a7e5d8b22c..b24567c547c 100644 --- a/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleDetailsMatchingInstances.tsx @@ -40,26 +40,44 @@ interface ShowMoreInstancesProps { stats: ShowMoreStats; onClick?: React.ComponentProps['onClick']; href?: React.ComponentProps['href']; + enableFiltering?: boolean; + alertState?: InstanceStateFilter; } -function ShowMoreInstances({ stats, onClick, href }: ShowMoreInstancesProps) { +function ShowMoreInstances({ stats, onClick, href, enableFiltering, alertState }: ShowMoreInstancesProps) { const styles = useStyles2(getStyles); const { visibleItemsCount, totalItemsCount } = stats; return (
- - Showing {{ visibleItemsCount }} out of {{ totalItemsCount }} instances - + {enableFiltering && alertState ? ( + + Showing {{ visibleItemsCount }} {{ alertState }} out of {{ totalItemsCount }} instances + + ) : ( + + Showing {{ visibleItemsCount }} out of {{ totalItemsCount }} instances + + )}
- - Show all {{ totalItemsCount }} alert instances - + {enableFiltering ? ( + Show all + ) : ( + + Show all {{ totalItemsCount }} alert instances + + )}
); @@ -128,6 +146,8 @@ export function RuleDetailsMatchingInstances(props: Props) { stats={stats} onClick={enableFiltering ? resetFilter : undefined} href={!enableFiltering ? ruleViewPageLink : undefined} + enableFiltering={enableFiltering} + alertState={alertState} /> ) : undefined; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 3ced02e0c08..bdf7943f90e 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -2334,8 +2334,10 @@ "label-tenant-sources": "Tenant sources" }, "rule-details-matching-instances": { - "button-show-all": "Show all {{totalItemsCount}} alert instances", - "showing-count": "Showing {{visibleItemsCount}} out of {{totalItemsCount}} instances" + "button-show-all": "Show all", + "button-show-all-instances": "Show all {{totalItemsCount}} alert instances", + "showing-count": "Showing {{visibleItemsCount}} out of {{totalItemsCount}} instances", + "showing-count-with-state": "Showing {{visibleItemsCount}} {{alertState}} out of {{totalItemsCount}} instances" }, "rule-editor": { "get-content": { From c71c0b33ee7a9b96200c0f72cce864020b26e86b Mon Sep 17 00:00:00 2001 From: Anna Urbiztondo Date: Thu, 11 Dec 2025 12:27:36 +0100 Subject: [PATCH 014/297] Docs: Configure Git Sync using CLI (#115068) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP * WIP * Edits, Claude * Prettier * Update docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md Co-authored-by: Roberto Jiménez Sánchez * Update docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md Co-authored-by: Roberto Jiménez Sánchez * WIP * Restructuring * Minor tweaks * Fix * Update docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md Co-authored-by: Roberto Jiménez Sánchez * Feedback * Prettier * Links --------- Co-authored-by: Roberto Jiménez Sánchez --- .../provision-resources/git-sync-setup.md | 326 +++++++++++++----- 1 file changed, 235 insertions(+), 91 deletions(-) diff --git a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md index f6113fe428d..758226c6257 100644 --- a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md +++ b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md @@ -29,60 +29,54 @@ You can sign up to the private preview using the [Git Sync early access form](ht {{< /admonition >}} -Git Sync lets you manage Grafana dashboards as code by storing dashboard JSON files and folders in a remote GitHub repository. - -To set up Git Sync and synchronize with a GitHub repository follow these steps: - -1. [Enable feature toggles in Grafana](#enable-required-feature-toggles) (first time set up). -1. [Create a GitHub access token](#create-a-github-access-token). -1. [Configure a connection to your GitHub repository](#set-up-the-connection-to-github). -1. [Choose what content to sync with Grafana](#choose-what-to-synchronize). - -Optionally, you can [extend Git Sync](#configure-webhooks-and-image-rendering) by enabling pull request notifications and image previews of dashboard changes. - -| Capability | Benefit | Requires | -| ----------------------------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------- | -| Adds a table summarizing changes to your pull request | Provides a convenient way to save changes back to GitHub. | Webhooks configured | -| Add a dashboard preview image to a PR | View a snapshot of dashboard changes to a pull request without opening Grafana. | Image renderer and webhooks configured | - -{{< admonition type="note" >}} - -Alternatively, you can configure a local file system instead of using GitHub. Refer to [Set up file provisioning](https://grafana.com/docs/grafana//observability-as-code/provision-resources/file-path-setup/) for more information. - -{{< /admonition >}} - -## Performance impacts of enabling Git Sync - -Git Sync is an experimental feature and is under continuous development. Reporting any issues you encounter can help us improve Git Sync. - -When Git Sync is enabled, the database load might increase, especially for instances with a lot of folders and nested folders. Evaluate the performance impact, if any, in a non-production environment. +This guide shows you how to set up Git Sync to synchronize your Grafana dashboards and folders with a GitHub repository. You'll set up Git Sync to enable version-controlled dashboard management either [using the UI](#set-up-git-sync-using-grafana-ui) or [as code](#set-up-git-sync-as-code). ## Before you begin -{{< admonition type="caution" >}} +Before you begin, ensure you have the following: -Refer to [Known limitations](https://grafana.com/docs/grafana//observability-as-code/provision-resources/intro-git-sync#known-limitations/) before using Git Sync. +- A Grafana instance (Cloud, OSS, or Enterprise). +- If you're [using webhooks or image rendering](#extend-git-sync-for-real-time-notification-and-image-rendering), a public instance with external access +- Administration rights in your Grafana organization +- A [GitHub private access token](#create-a-github-access-token) +- A GitHub repository to store your dashboards in +- Optional: The [Image Renderer service](https://github.com/grafana/grafana-image-renderer) to save image previews with your PRs + +### Known limitations + +Refer to [Known limitations](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/intro-git-sync#known-limitations) before using Git Sync. + +Refer to [Supported resources](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/intro-git-sync#supported-resources) for details about which resources you can sync. + +### Performance considerations + +When Git Sync is enabled, the database load might increase, especially for instances with many folders and nested folders. Evaluate the performance impact, if any, in a non-production environment. + +Git Sync is under continuous development. [Report any issues](https://grafana.com/help/) you encounter to help us improve Git Sync. + +## Set up Git Sync + +To set up Git Sync and synchronize with a GitHub repository, follow these steps: + +1. [Enable feature toggles in Grafana](#enable-required-feature-toggles) (first time setup) +1. [Create a GitHub access token](#create-a-github-access-token) +1. Set up Git Sync [using the UI](#set-up-git-sync-using-grafana-ui) or [as code](#set-up-git-sync-as-code) + +After setup, you can [verify your dashboards](#verify-your-dashboards-in-grafana). + +Optionally, you can also [extend Git Sync with webhooks and image rendering](#extend-git-sync-for-real-time-notification-and-image-rendering). + +{{< admonition type="note" >}} + +Alternatively, you can configure a local file system instead of using GitHub. Refer to [Set up file provisioning](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/file-path-setup/) for more information. {{< /admonition >}} -### Requirements - -To set up Git Sync, you need: - -- Administration rights in your Grafana organization. -- Enable the required feature toggles in your Grafana instance. Refer to [Enable required feature toggles](#enable-required-feature-toggles) for instructions. -- A GitHub repository to store your dashboards in. - - If you want to use a local file path, refer to [the local file path guide](https://grafana.com/docs/grafana//observability-as-code/provision-resources/file-path-setup/). -- A GitHub access token. The Grafana UI will prompt you during setup. -- Optional: A public Grafana instance. -- Optional: The [Image Renderer service](https://github.com/grafana/grafana-image-renderer) to save image previews with your PRs. - ## Enable required feature toggles -To activate Git Sync in Grafana, you need to enable the `provisioning` and `kubernetesDashboards` feature toggles. -For additional information about feature toggles, refer to [Configure feature toggles](https://grafana.com/docs/grafana//setup-grafana/configure-grafana/feature-toggles). +To activate Git Sync in Grafana, you need to enable the `provisioning` and `kubernetesDashboards` feature toggles. For more information about feature toggles, refer to [Configure feature toggles](https://grafana.com/docs/grafana//setup-grafana/configure-grafana/feature-toggles/#experimental-feature-toggles). -To enable the required feature toggles, add them to your Grafana configuration file: +To enable the required feature toggles: 1. Open your Grafana configuration file, either `grafana.ini` or `custom.ini`. For file location based on operating system, refer to [Configuration file location](https://grafana.com/docs/grafana//setup-grafana/configure-grafana/feature-toggles/#experimental-feature-toggles). 1. Locate or add a `[feature_toggles]` section. Add these values: @@ -97,8 +91,9 @@ To enable the required feature toggles, add them to your Grafana configuration f ## Create a GitHub access token -Whenever you connect to a GitHub repository, you need to create a GitHub access token with specific repository permissions. -This token needs to be added to your Git Sync configuration to enable read and write permissions between Grafana and GitHub repository. +Whenever you connect to a GitHub repository, you need to create a GitHub access token with specific repository permissions. This token needs to be added to your Git Sync configuration to enable read and write permissions between Grafana and GitHub repository. + +To create a GitHub access token: 1. Create a new token using [Create new fine-grained personal access token](https://github.com/settings/personal-access-tokens/new). Refer to [Managing your personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) for instructions. 1. Under **Permissions**, expand **Repository permissions**. @@ -112,19 +107,23 @@ This token needs to be added to your Git Sync configuration to enable read and w 1. Verify the options and select **Generate token**. 1. Copy the access token. Leave the browser window available with the token until you've completed configuration. -GitHub Apps are not currently supported. +GitHub Apps aren't currently supported. -## Set up the connection to GitHub +## Set up Git Sync using Grafana UI -Use **Provisioning** to guide you through setting up Git Sync to use a GitHub repository. +1. [Configure a connection to your GitHub repository](#set-up-the-connection-to-github) +1. [Choose what content to sync with Grafana](#choose-what-to-synchronize) +1. [Choose additional settings](#choose-additional-settings) + +### Set up the connection to GitHub + +Use **Provisioning** to guide you through setting up Git Sync to use a GitHub repository: 1. Log in to your Grafana server with an account that has the Grafana Admin flag set. 1. Select **Administration** in the left-side menu and then **Provisioning**. 1. Select **Configure Git Sync**. -### Connect to external storage - -To connect your GitHub repository, follow these steps: +To connect your GitHub repository: 1. Paste your GitHub personal access token into **Enter your access token**. Refer to [Create a GitHub access token](#create-a-github-access-token) for instructions. 1. Paste the **Repository URL** for your GitHub repository into the text box. @@ -134,32 +133,12 @@ To connect your GitHub repository, follow these steps: ### Choose what to synchronize -In this step you can decide which elements to synchronize. Keep in mind the available options depend on the status of your Grafana instance. +In this step, you can decide which elements to synchronize. The available options depend on the status of your Grafana instance: - If the instance contains resources in an incompatible data format, you'll have to migrate all the data using instance sync. Folder sync won't be supported. -- If there is already another connection using folder sync, instance sync won't be offered. +- If there's already another connection using folder sync, instance sync won't be offered. -#### Synchronization limitations - -Git Sync only supports dashboards and folders. Alerts, panels, and other resources are not supported yet. - -{{< admonition type="caution" >}} - -Refer to [Known limitations](https://grafana.com/docs/grafana//observability-as-code/provision-resources/intro-git-sync#known-limitations/) before using Git Sync. Refer to [Supported resources](/docs/grafana//observability-as-code/provision-resources/intro-git-sync#supported-resources) for details about which resources you can sync. - -{{< /admonition >}} - -Full instance sync is not available in Grafana Cloud. - -In Grafana OSS/Enterprise: - -- If you try to perform a full instance sync with resources that contain alerts or panels, Git Sync will block the connection. -- You won't be able to create new alerts or library panels after the setup is completed. -- If you opted for full instance sync and want to use alerts and library panels, you'll have to delete the synced repository and connect again with folder sync. - -#### Set up synchronization - -To set up synchronization, choose to either sync your entire organization resources with external storage, or to sync certain resources to a new Grafana folder (with up to 10 connections). +To set up synchronization: - Choose **Sync all resources with external storage** if you want to sync and manage your entire Grafana instance through external storage. With this option, all of your dashboards are synced to that one repository. You can only have one provisioned connection with this selection, and you won't have the option of setting up additional repositories to connect to. - Choose **Sync external storage to new Grafana folder** to sync external resources into a new folder without affecting the rest of your instance. You can repeat this process for up to 10 connections. @@ -170,20 +149,183 @@ Next, enter a **Display name** for the repository connection. Resources stored i Finally, you can set up how often your configured storage is polled for updates. +To configure additional settings: + 1. For **Update instance interval (seconds)**, enter how often you want the instance to pull updates from GitHub. The default value is 60 seconds. 1. Optional: Select **Read only** to ensure resources can't be modified in Grafana. -1. Optional: If you have the Grafana Image Renderer plugin configured, you can **Enable dashboards previews in pull requests**. If image rendering is not available, then you can't select this option. For more information, refer to the [Image Renderer service](https://github.com/grafana/grafana-image-renderer). +1. Optional: If you have the Grafana Image Renderer plugin configured, you can **Enable dashboards previews in pull requests**. If image rendering isn't available, then you can't select this option. For more information, refer to the [Image Renderer service](https://github.com/grafana/grafana-image-renderer). 1. Select **Finish** to proceed. +### Modify your configuration after setup is complete + +To update your repository configuration after you've completed setup: + +1. Log in to your Grafana server with an account that has the Grafana Admin flag set. +1. Select **Administration** in the left-side menu and then **Provisioning**. +1. Select **Settings** for the repository you wish to modify. +1. Use the **Configure repository** screen to update any of the settings. +1. Select **Save** to preserve the updates. + +## Set up Git Sync as code + +Alternatively, you can also configure Git Sync using `grafanactl`. Since Git Sync configuration is managed as code using Custom Resource Definitions (CRDs), you can create a Repository CRD in a YAML file and use `grafanactl` to push it to Grafana. This approach enables automated, GitOps-style workflows for managing Git Sync configuration instead of using the Grafana UI. + +To set up Git Sync with `grafanactl`, follow these steps: + +1. [Create the repository CRD](#create-the-repository-crd) +1. [Push the repository CRD to Grafana](#push-the-repository-crd-to-grafana) +1. [Manage repository resources](#manage-repository-resources) +1. [Verify setup](#verify-setup) + +For more information, refer to the following documents: + +- [grafanactl Documentation](https://grafana.github.io/grafanactl/) +- [Repository CRD Reference](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/git-sync-setup/) +- [Dashboard CRD Format](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/export-resources/) + +### Create the repository CRD + +Create a `repository.yaml` file defining your Git Sync configuration: + +```yaml +apiVersion: provisioning.grafana.app/v0alpha1 +kind: Repository +metadata: + name: +spec: + title: + type: github + github: + url: + branch: + path: grafana/ + generateDashboardPreviews: true + sync: + enabled: true + intervalSeconds: 60 + target: folder + workflows: + - write + - branch +secure: + token: + create: +``` + +Replace the placeholders with your values: + +- _``_: Unique identifier for this repository resource +- _``_: Human-readable name displayed in Grafana UI +- _``_: GitHub repository URL +- _``_: Branch to sync +- _``_: GitHub Personal Access Token + +{{< admonition type="note" >}} + +Only `target: folder` is currently supported for Git Sync. + +{{< /admonition >}} + +#### Configuration parameters + +The following configuration parameters are available: + +| Field | Description | +| --------------------------------------- | ----------------------------------------------------------- | +| `metadata.name` | Unique identifier for this repository resource | +| `spec.title` | Human-readable name displayed in Grafana UI | +| `spec.type` | Repository type (`github`) | +| `spec.github.url` | GitHub repository URL | +| `spec.github.branch` | Branch to sync | +| `spec.github.path` | Directory path containing dashboards | +| `spec.github.generateDashboardPreviews` | Generate preview images (true/false) | +| `spec.sync.enabled` | Enable synchronization (true/false) | +| `spec.sync.intervalSeconds` | Sync interval in seconds | +| `spec.sync.target` | Where to place synced dashboards (`folder`) | +| `spec.workflows` | Enabled workflows: `write` (direct commits), `branch` (PRs) | +| `secure.token.create` | GitHub Personal Access Token | + +### Push the repository CRD to Grafana + +Before pushing any resources, configure `grafanactl` with your Grafana instance details. Refer to the [grafanactl configuration documentation](https://grafana.github.io/grafanactl/) for setup instructions. + +Push the repository configuration: + +```sh +grafanactl resources push --path +``` + +The `--path` parameter has to point to the directory containing your `repository.yaml` file. + +After pushing, Grafana will: + +1. Create the repository resource +1. Connect to your GitHub repository +1. Pull dashboards from the specified path +1. Begin syncing at the configured interval + +### Manage repository resources + +#### List repositories + +To list all repositories: + +```sh +grafanactl resources get repositories +``` + +#### Get repository details + +To get details for a specific repository: + +```sh +grafanactl resources get repository/ +grafanactl resources get repository/ -o json +grafanactl resources get repository/ -o yaml +``` + +#### Update the repository + +To update a repository: + +```sh +grafanactl resources edit repository/ +``` + +#### Delete the repository + +To delete a repository: + +```sh +grafanactl resources delete repository/ +``` + +### Verify setup + +Check that Git Sync is working: + +```sh +# List repositories +grafanactl resources get repositories + +# Check Grafana UI +# Navigate to: Administration → Provisioning → Git Sync +``` + ## Verify your dashboards in Grafana To verify that your dashboards are available at the location that you specified, click **Dashboards**. The name of the dashboard is listed in the **Name** column. -Now that your dashboards have been synced from a repository, you can customize the name, change the branch, and create a pull request (PR) for it. Refer to [Manage provisioned repositories with Git Sync](https://grafana.com/docs/grafana//observability-as-code/provision-resources/use-git-sync/) for more information. +Now that your dashboards have been synced from a repository, you can customize the name, change the branch, and create a pull request (PR) for it. Refer to [Manage provisioned repositories with Git Sync](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/use-git-sync/) for more information. -## Configure webhooks and image rendering +## Extend Git Sync for real-time notification and image rendering -You can extend Git Sync by getting instant updates and pull requests using webhooks and add dashboard previews in pull requests. +Optionally, you can extend Git Sync by enabling pull request notifications and image previews of dashboard changes. + +| Capability | Benefit | Requires | +| ----------------------------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------- | +| Adds a table summarizing changes to your pull request | Provides a convenient way to save changes back to GitHub | Webhooks configured | +| Add a dashboard preview image to a PR | View a snapshot of dashboard changes to a pull request without opening Grafana | Image renderer and webhooks configured | ### Set up webhooks for realtime notification and pull request integration @@ -191,25 +333,26 @@ When connecting to a GitHub repository, Git Sync uses webhooks to enable real-ti You can set up webhooks with whichever service or tooling you prefer. You can use Cloudflare Tunnels with a Cloudflare-managed domain, port-forwarding and DNS options, or a tool such as `ngrok`. -To set up webhooks you need to expose your Grafana instance to the public Internet. You can do this via port forwarding and DNS, a tool such as `ngrok`, or any other method you prefer. The permissions set in your GitHub access token provide the authorization for this communication. +To set up webhooks, you need to expose your Grafana instance to the public Internet. You can do this via port forwarding and DNS, a tool such as `ngrok`, or any other method you prefer. The permissions set in your GitHub access token provide the authorization for this communication. After you have the public URL, you can add it to your Grafana configuration file: -```yaml +```ini [server] -root_url = https://PUBLIC_DOMAIN.HERE +root_url = https:// ``` +Replace _``_ with your public domain. + To check the configured webhooks, go to **Administration** > **Provisioning** and click the **View** link for your GitHub repository. #### Expose necessary paths only -If your security setup does not permit publicly exposing the Grafana instance, you can either choose to `allowlist` the GitHub IP addresses, or expose only the necessary paths. +If your security setup doesn't permit publicly exposing the Grafana instance, you can either choose to allowlist the GitHub IP addresses, or expose only the necessary paths. The necessary paths required to be exposed are, in RegExp: - `/apis/provisioning\.grafana\.app/v0(alpha1)?/namespaces/[^/]+/repositories/[^/]+/(webhook|render/.*)$` - ### Set up image rendering for dashboard previews @@ -217,12 +360,13 @@ Set up image rendering to add visual previews of dashboard updates directly in p To enable this capability, install the Grafana Image Renderer in your Grafana instance. For more information and installation instructions, refer to the [Image Renderer service](https://github.com/grafana/grafana-image-renderer). -## Modify configurations after set up is complete +## Next steps -To update your repository configuration after you've completed set up: +You've successfully set up Git Sync to manage your Grafana dashboards through version control. Your dashboards are now synchronized with a GitHub repository, enabling collaborative development and change tracking. -1. Log in to your Grafana server with an account that has the Grafana Admin flag set. -1. Select **Administration** in the left-side menu and then **Provisioning**. -1. Select **Settings** for the repository you wish to modify. -1. Use the **Configure repository** screen to update any of the settings. -1. Select **Save** to preserve the updates. +To learn more about using Git Sync: + +- [Work with provisioned dashboards](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/provisioned-dashboards/) +- [Manage provisioned repositories with Git Sync](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/use-git-sync/) +- [Export resources](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/export-resources/) +- [grafanactl documentation](https://grafana.github.io/grafanactl/) From eef14d2cee3ad73ed830f34f84f4e51bde82df2d Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 11 Dec 2025 15:33:34 +0300 Subject: [PATCH 015/297] Dependencies: update glob@npm for dependabot (#115146) --- package.json | 1 - yarn.lock | 42 ++++++++++++++++-------------------------- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 683ed2373de..8be20ab399f 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,6 @@ "@types/eslint": "9.6.1", "@types/eslint-scope": "^8.0.0", "@types/file-saver": "2.0.7", - "@types/glob": "^9.0.0", "@types/google.analytics": "^0.0.46", "@types/gtag.js": "^0.0.20", "@types/history": "4.7.11", diff --git a/yarn.lock b/yarn.lock index eefd9719059..91cbadc61e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10421,15 +10421,6 @@ __metadata: languageName: node linkType: hard -"@types/glob@npm:^9.0.0": - version: 9.0.0 - resolution: "@types/glob@npm:9.0.0" - dependencies: - glob: "npm:*" - checksum: 10/a9ea3afe1eafbc8fb303d2d39cd786084aece75fd8eeae1bad8febbf6e0323b429145f31e779a3d68fa693b2a53648ec2c639ee4858fb29132f801c74678051c - languageName: node - linkType: hard - "@types/google.analytics@npm:^0.0.46": version: 0.0.46 resolution: "@types/google.analytics@npm:0.0.46" @@ -19135,22 +19126,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:*, glob@npm:12.0.0": - version: 12.0.0 - resolution: "glob@npm:12.0.0" - dependencies: - foreground-child: "npm:^3.3.1" - jackspeak: "npm:^4.1.1" - minimatch: "npm:^10.1.1" - minipass: "npm:^7.1.2" - package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^2.0.0" - bin: - glob: dist/esm/bin.mjs - checksum: 10/6e21b3f1f1fa635836d45e54bbe50704884cc3e310e0cc011cfb5429db65a030e12936d99b07e66236370efe45dc8c8b26fa5334dbf555d6f8709e0315c77c30 - languageName: node - linkType: hard - "glob@npm:10.4.1": version: 10.4.1 resolution: "glob@npm:10.4.1" @@ -19198,6 +19173,22 @@ __metadata: languageName: node linkType: hard +"glob@npm:12.0.0": + version: 12.0.0 + resolution: "glob@npm:12.0.0" + dependencies: + foreground-child: "npm:^3.3.1" + jackspeak: "npm:^4.1.1" + minimatch: "npm:^10.1.1" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^2.0.0" + bin: + glob: dist/esm/bin.mjs + checksum: 10/6e21b3f1f1fa635836d45e54bbe50704884cc3e310e0cc011cfb5429db65a030e12936d99b07e66236370efe45dc8c8b26fa5334dbf555d6f8709e0315c77c30 + languageName: node + linkType: hard + "glob@npm:^7.0.3, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.1.7": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -19502,7 +19493,6 @@ __metadata: "@types/eslint": "npm:9.6.1" "@types/eslint-scope": "npm:^8.0.0" "@types/file-saver": "npm:2.0.7" - "@types/glob": "npm:^9.0.0" "@types/google.analytics": "npm:^0.0.46" "@types/gtag.js": "npm:^0.0.20" "@types/history": "npm:4.7.11" From e6b5ece559c62708e57c672b02fe5073c5b9c61f Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Thu, 11 Dec 2025 13:38:02 +0100 Subject: [PATCH 016/297] Plugins Preinstall: Fix URL parsing when includes basic auth (#115143) Preinstall: Fix URL setting when includes basic auth --- pkg/setting/setting_plugins.go | 2 +- pkg/setting/setting_plugins_test.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/setting/setting_plugins.go b/pkg/setting/setting_plugins.go index a6842a276a0..5a83c3b1f5a 100644 --- a/pkg/setting/setting_plugins.go +++ b/pkg/setting/setting_plugins.go @@ -102,7 +102,7 @@ func (cfg *Cfg) processPreinstallPlugins(rawInstallPlugins []string, preinstallP if len(parts) > 1 { version = parts[1] if len(parts) > 2 { - url = parts[2] + url = strings.Join(parts[2:], "@") } } diff --git a/pkg/setting/setting_plugins_test.go b/pkg/setting/setting_plugins_test.go index 9b0df2aa583..4c29e5d3fdc 100644 --- a/pkg/setting/setting_plugins_test.go +++ b/pkg/setting/setting_plugins_test.go @@ -210,6 +210,11 @@ func Test_readPluginSettings(t *testing.T) { rawInput: "plugin1@@https://example.com/plugin1.tar.gz", expected: append(defaultPreinstallPluginsList, InstallPlugin{ID: "plugin1", Version: "", URL: "https://example.com/plugin1.tar.gz"}), }, + { + name: "should parse a plugin with credentials in the URL", + rawInput: "plugin1@@https://username:password@example.com/plugin1.tar.gz", + expected: append(defaultPreinstallPluginsList, InstallPlugin{ID: "plugin1", Version: "", URL: "https://username:password@example.com/plugin1.tar.gz"}), + }, { name: "when preinstall_async is false, should add all plugins to preinstall_sync", rawInput: "plugin1", From 350c3578c74014e332c5d461161b798c5c9a4ddd Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Thu, 11 Dec 2025 13:54:30 +0100 Subject: [PATCH 017/297] Dynamic dashboards: Update variable set state when variable hide property changes (#115094) fix: update variable set state when variable hide property changes When changing a variable's positioning to show in controls menu using the edit side pane, the state of dashboardControls does not immediately update. This makes it seem to the user that nothing was changed. The issue was that when a variable's hide property changes, only the variable's state was updated, but not the parent SceneVariableSet state. Components that subscribe to the variable set state (like useDashboardControls) didn't detect the change because the variables array reference remained the same. This fix updates the parent SceneVariableSet state when a variable's hide property changes, ensuring components that subscribe to the variable set will re-render immediately. Co-authored-by: grafakus --- .../dashboard-scene/edit-pane/shared.ts | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard-scene/edit-pane/shared.ts b/public/app/features/dashboard-scene/edit-pane/shared.ts index 39fd5fec9a7..2a60fb33546 100644 --- a/public/app/features/dashboard-scene/edit-pane/shared.ts +++ b/public/app/features/dashboard-scene/edit-pane/shared.ts @@ -245,10 +245,29 @@ export const dashboardEditActions = { description: t('dashboard.variable.description.action', 'Change variable description'), prop: 'description', }), - changeVariableHideValue: makeEditAction({ - description: t('dashboard.variable.hide.action', 'Change variable hide option'), - prop: 'hide', - }), + changeVariableHideValue({ source, oldValue, newValue }: EditActionProps) { + const variableSet = source.parent; + const variablesBeforeChange = + variableSet instanceof SceneVariableSet ? [...(variableSet.state.variables ?? [])] : undefined; + + dashboardEditActions.edit({ + description: t('dashboard.variable.hide.action', 'Change variable hide option'), + source, + perform: () => { + source.setState({ hide: newValue }); + // Updating the variables set since components that show/hide variables subscribe to the variable set, not the individual variables. + if (variableSet instanceof SceneVariableSet) { + variableSet.setState({ variables: [...(variableSet.state.variables ?? [])] }); + } + }, + undo: () => { + source.setState({ hide: oldValue }); + if (variableSet instanceof SceneVariableSet && variablesBeforeChange) { + variableSet.setState({ variables: variablesBeforeChange }); + } + }, + }); + }, moveElement(props: MoveElementActionHelperProps) { const { movedObject, source, perform, undo } = props; From b6ba8a0fd4369e9b74a893d16a644e6a973b9664 Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Thu, 11 Dec 2025 13:55:03 +0100 Subject: [PATCH 018/297] Dashboards: Make variables selectable in controls menu (#115092) * Dashboard: Make variables selectable in controls menu and improve spacing - Add selection support for variables in controls menu (onPointerDown handler and selection classes) - Add padding to variables and annotations in controls menu (theme.spacing(1)) - Reduce menu container padding from 1.5 to 1 - Remove margins between menu items * fix: remove unused imports in DashboardControlsMenu --- .../scene/DataLayerControl.tsx | 1 + .../scene/VariableControls.tsx | 30 ++++++++++++++++--- .../DashboardControlsMenu.tsx | 15 ++++------ 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/DataLayerControl.tsx b/public/app/features/dashboard-scene/scene/DataLayerControl.tsx index 3fc19bb2a3d..21691a0c2f9 100644 --- a/public/app/features/dashboard-scene/scene/DataLayerControl.tsx +++ b/public/app/features/dashboard-scene/scene/DataLayerControl.tsx @@ -64,6 +64,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ display: 'flex', alignItems: 'center', gap: theme.spacing(1), + padding: theme.spacing(1), }), controlWrapper: css({ height: theme.spacing(2), diff --git a/public/app/features/dashboard-scene/scene/VariableControls.tsx b/public/app/features/dashboard-scene/scene/VariableControls.tsx index 3509848c442..1061938e5e6 100644 --- a/public/app/features/dashboard-scene/scene/VariableControls.tsx +++ b/public/app/features/dashboard-scene/scene/VariableControls.tsx @@ -82,19 +82,39 @@ export function VariableValueSelectWrapper({ variable, inMenu }: VariableSelectP // For switch variables in menu, we want to show the switch on the left and the label on the right if (inMenu && sceneUtils.isSwitchVariable(variable)) { return ( -
+
- +
); } if (inMenu) { return ( -
- +
+
); @@ -164,11 +184,13 @@ const getStyles = (theme: GrafanaTheme2) => ({ verticalContainer: css({ display: 'flex', flexDirection: 'column', + padding: theme.spacing(1), }), switchMenuContainer: css({ display: 'flex', alignItems: 'center', gap: theme.spacing(1), + padding: theme.spacing(1), }), switchControl: css({ '& > div': { diff --git a/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenu.tsx b/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenu.tsx index 9d27ee01f0f..edec4d05659 100644 --- a/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenu.tsx +++ b/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenu.tsx @@ -1,4 +1,4 @@ -import { css, cx } from '@emotion/css'; +import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { SceneDataLayerProvider, SceneVariable } from '@grafana/scenes'; @@ -17,8 +17,6 @@ interface DashboardControlsMenuProps { } export function DashboardControlsMenu({ variables, links, annotations, dashboardUID }: DashboardControlsMenuProps) { - const styles = useStyles2(getStyles); - return ( { // Normally, clicking the overlay closes the dropdown. @@ -39,7 +37,7 @@ export function DashboardControlsMenu({ variables, links, annotations, dashboard > {/* Variables */} {variables.map((variable, index) => ( -
0 })} key={variable.state.key}> +
))} @@ -47,7 +45,7 @@ export function DashboardControlsMenu({ variables, links, annotations, dashboard {/* Annotation layers */} {annotations.length > 0 && annotations.map((layer, index) => ( -
0 || index > 0 })} key={layer.state.key}> +
))} @@ -79,10 +77,7 @@ function MenuDivider() { const getStyles = (theme: GrafanaTheme2) => ({ divider: css({ - marginTop: theme.spacing(2), + marginTop: theme.spacing(1), padding: theme.spacing(0, 0.5), }), - menuItem: css({ - marginTop: theme.spacing(2), - }), }); From 73de5f98e1627f35e9145630cf396b1f5327c31e Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Thu, 11 Dec 2025 14:06:09 +0100 Subject: [PATCH 019/297] Assistant: Update origin for analyze-rule-menu-item (#115147) * Assistant: Update origin for analyze-rule-menu-item * Update origin, not test id --- .../alerting/unified/components/assistant/AnalizeRuleButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/alerting/unified/components/assistant/AnalizeRuleButton.tsx b/public/app/features/alerting/unified/components/assistant/AnalizeRuleButton.tsx index 6438ceb70de..ba37a05cda1 100644 --- a/public/app/features/alerting/unified/components/assistant/AnalizeRuleButton.tsx +++ b/public/app/features/alerting/unified/components/assistant/AnalizeRuleButton.tsx @@ -59,7 +59,7 @@ function AnalyzeRuleButtonView({ }); openAssistant({ - origin: 'alerting', + origin: 'alerting/analyze-rule-menu-item', mode: 'assistant', prompt: analyzeRulePrompt, context: [alertContext], From 0c82f92539a79cd916c240e3d4cfbb4ede6559e5 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 11 Dec 2025 14:35:27 +0100 Subject: [PATCH 020/297] NPM: Attempt to fix e2e-selectors dist-tag after OIDC migration (#115012) * fetch oidc token from github * use same approach as electron --- scripts/publish-npm-packages.sh | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/scripts/publish-npm-packages.sh b/scripts/publish-npm-packages.sh index 34dc06fc009..6ee770ee840 100755 --- a/scripts/publish-npm-packages.sh +++ b/scripts/publish-npm-packages.sh @@ -68,6 +68,47 @@ if (( CHANGES_COUNT > 0 )); then TAGS=$(npm dist-tag ls @grafana/e2e-selectors) if [[ $TAGS =~ $regex_pattern ]]; then echo "$CHANGES_COUNT file(s) in packages/grafana-e2e-selectors were changed. Adding 'modified' tag to @grafana/e2e-selectors@${BASH_REMATCH[1]}" + + # If using OIDC, exchange token for npm auth (npm publish handles OIDC internally, + # but dist-tag requires explicit authentication) + # Reference: https://github.com/electron/npm-trusted-auth-action + if [ -n "$ACTIONS_ID_TOKEN_REQUEST_URL" ] && [ -n "$ACTIONS_ID_TOKEN_REQUEST_TOKEN" ]; then + echo "Fetching GitHub OIDC token..." + OIDC_TOKEN=$(curl -sS -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ + "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=npm:registry.npmjs.org" | jq -r '.value // empty') + + if [ -z "$OIDC_TOKEN" ]; then + echo "Warning: Failed to fetch OIDC token, dist-tag operation may fail" + else + # Mask the OIDC token so it won't appear in logs + echo "::add-mask::$OIDC_TOKEN" + echo "Exchanging OIDC token for npm auth token..." + ENCODED_PACKAGE=$(printf "%s" "@grafana/e2e-selectors" | jq -Rr @uri) + RESPONSE=$(curl -sS -X POST \ + -H "Authorization: Bearer $OIDC_TOKEN" \ + -H "Accept: application/json" \ + -w "\n%{http_code}" \ + "https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/$ENCODED_PACKAGE") + + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + BODY=$(echo "$RESPONSE" | sed '$d') + + if [ "$HTTP_CODE" != "201" ]; then + echo "Warning: Failed to exchange token. Status: $HTTP_CODE" + else + NPM_AUTH_TOKEN=$(echo "$BODY" | jq -r '.token // empty') + if [ -n "$NPM_AUTH_TOKEN" ]; then + # Mask the token so it won't appear in logs + echo "::add-mask::$NPM_AUTH_TOKEN" + echo "Configuring npm auth token in ~/.npmrc" + echo "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}" >> ~/.npmrc + else + echo "Warning: No token in response, dist-tag operation may fail" + fi + fi + fi + fi + npm dist-tag add @grafana/e2e-selectors@"${BASH_REMATCH[1]}" modified fi fi From 4ab198b201acc42077463518d1697ed79a0ce77e Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 11 Dec 2025 15:00:54 +0100 Subject: [PATCH 021/297] E2E Selectors: Fix package description (#115148) dummie change --- packages/grafana-e2e-selectors/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-e2e-selectors/src/index.ts b/packages/grafana-e2e-selectors/src/index.ts index 32b22b5bb52..5b078f1233f 100644 --- a/packages/grafana-e2e-selectors/src/index.ts +++ b/packages/grafana-e2e-selectors/src/index.ts @@ -1,5 +1,5 @@ /** - * A library containing the different design components of the Grafana ecosystem. + * A library containing e2e selectors for the Grafana ecosystem. * * @packageDocumentation */ From 73bcfbcc7414a2348185adffc1a39594b68a15a4 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Thu, 11 Dec 2025 16:05:13 +0100 Subject: [PATCH 022/297] Alerting: Collate alert_rule.namespace_uid column as binary (#115152) Alerting: Collate namespace_uid column as binary --- pkg/services/sqlstore/migrations/migrations.go | 2 ++ .../ualert/alert_rule_namespace_collation.go | 10 ++++++++++ 2 files changed, 12 insertions(+) create mode 100644 pkg/services/sqlstore/migrations/ualert/alert_rule_namespace_collation.go diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index e713525c0f6..9f0191dc9ff 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -165,5 +165,7 @@ func (oss *OSSMigrations) AddMigration(mg *Migrator) { ualert.AddStateAnnotationsColumn(mg) + ualert.CollateBinAlertRuleNamespace(mg) + ualert.CollateBinAlertRuleGroup(mg) } diff --git a/pkg/services/sqlstore/migrations/ualert/alert_rule_namespace_collation.go b/pkg/services/sqlstore/migrations/ualert/alert_rule_namespace_collation.go new file mode 100644 index 00000000000..2766f337baa --- /dev/null +++ b/pkg/services/sqlstore/migrations/ualert/alert_rule_namespace_collation.go @@ -0,0 +1,10 @@ +package ualert + +import "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + +// CollateBinAlertRuleNamespace ensures that namespace_uid column collates in the same way go sorts strings. +func CollateBinAlertRuleNamespace(mg *migrator.Migrator) { + mg.AddMigration("ensure namespace_uid column sorts the same way as golang", migrator.NewRawSQLMigration(""). + Mysql("ALTER TABLE alert_rule MODIFY namespace_uid VARCHAR(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL;"). + Postgres(`ALTER TABLE alert_rule ALTER COLUMN namespace_uid SET DATA TYPE varchar(40) COLLATE "C";`)) +} From 5d7b9c5050111b7b7d6725945d8d8cb61bf3914b Mon Sep 17 00:00:00 2001 From: Andres Torres Date: Thu, 11 Dec 2025 10:24:01 -0500 Subject: [PATCH 023/297] fix(setting): Replacing dynamic client to reduce memory footprint (#115125) --- pkg/services/setting/service.go | 252 ++++++++---- pkg/services/setting/service_test.go | 581 +++++++++++---------------- 2 files changed, 403 insertions(+), 430 deletions(-) diff --git a/pkg/services/setting/service.go b/pkg/services/setting/service.go index 5afc9e22159..6b49abcb24f 100644 --- a/pkg/services/setting/service.go +++ b/pkg/services/setting/service.go @@ -2,27 +2,26 @@ package setting import ( "context" + "encoding/json" "fmt" + "io" "net/http" "time" - "github.com/grafana/grafana/pkg/infra/tracing" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" "gopkg.in/ini.v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/client-go/dynamic" - clientrest "k8s.io/client-go/rest" + "k8s.io/client-go/rest" "k8s.io/client-go/transport" authlib "github.com/grafana/authlib/authn" logging "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/semconv" ) @@ -38,18 +37,11 @@ const ( ApiGroup = "setting.grafana.app" apiVersion = "v0alpha1" resource = "settings" - kind = "Setting" - listKind = "SettingList" ) -var settingGroupVersion = schema.GroupVersionResource{ - Group: ApiGroup, - Version: apiVersion, - Resource: resource, -} - -var settingGroupListKind = map[schema.GroupVersionResource]string{ - settingGroupVersion: listKind, +var settingGroupVersion = schema.GroupVersion{ + Group: ApiGroup, + Version: apiVersion, } type remoteSettingServiceMetrics struct { @@ -106,10 +98,10 @@ type Service interface { } type remoteSettingService struct { - dynamicClient dynamic.Interface - log logging.Logger - pageSize int64 - metrics remoteSettingServiceMetrics + restClient *rest.RESTClient + log logging.Logger + pageSize int64 + metrics remoteSettingServiceMetrics } var _ Service = (*remoteSettingService)(nil) @@ -126,7 +118,7 @@ type Config struct { // At least one of WrapTransport or TokenExchangeClient is required. WrapTransport transport.WrapperFunc // TLSClientConfig configures TLS for the client connection. - TLSClientConfig clientrest.TLSClientConfig + TLSClientConfig rest.TLSClientConfig // QPS limits requests per second (defaults to DefaultQPS). QPS float32 // Burst allows request bursts above QPS (defaults to DefaultBurst). @@ -145,29 +137,39 @@ type Setting struct { Value string `json:"value"` } +// settingResource represents a single Setting resource from the K8s API. +type settingResource struct { + Spec Setting `json:"spec"` +} + +// settingListMetadata contains pagination info from the K8s list response. +type settingListMetadata struct { + Continue string `json:"continue,omitempty"` +} + // New creates a Service from the provided configuration. func New(config Config) (Service, error) { log := logging.New(LogPrefix) - dynamicClient, err := getDynamicClient(config, log) + + restClient, err := getRestClient(config, log) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create REST client: %w", err) } + pageSize := DefaultPageSize if config.PageSize > 0 { pageSize = config.PageSize } - metrics := initMetrics() - return &remoteSettingService{ - dynamicClient: dynamicClient, - pageSize: pageSize, - log: log, - metrics: metrics, + restClient: restClient, + log: log, + pageSize: pageSize, + metrics: initMetrics(), }, nil } -func (m *remoteSettingService) ListAsIni(ctx context.Context, labelSelector metav1.LabelSelector) (*ini.File, error) { +func (s *remoteSettingService) ListAsIni(ctx context.Context, labelSelector metav1.LabelSelector) (*ini.File, error) { namespace, ok := request.NamespaceFrom(ctx) ns := semconv.GrafanaNamespaceName(namespace) ctx, span := tracer.Start(ctx, "remoteSettingService.ListAsIni", @@ -178,33 +180,34 @@ func (m *remoteSettingService) ListAsIni(ctx context.Context, labelSelector meta return nil, tracing.Errorf(span, "missing namespace in context") } - settings, err := m.List(ctx, labelSelector) + settings, err := s.List(ctx, labelSelector) if err != nil { return nil, err } - iniFile, err := m.toIni(settings) + iniFile, err := toIni(settings) if err != nil { return nil, tracing.Error(span, err) } return iniFile, nil } -func (m *remoteSettingService) List(ctx context.Context, labelSelector metav1.LabelSelector) ([]*Setting, error) { +func (s *remoteSettingService) List(ctx context.Context, labelSelector metav1.LabelSelector) ([]*Setting, error) { namespace, ok := request.NamespaceFrom(ctx) ns := semconv.GrafanaNamespaceName(namespace) ctx, span := tracer.Start(ctx, "remoteSettingService.List", trace.WithAttributes(ns)) defer span.End() + if !ok || namespace == "" { return nil, tracing.Errorf(span, "missing namespace in context") } - log := m.log.FromContext(ctx).New(ns.Key, ns.Value, "function", "remoteSettingService.List", "traceId", span.SpanContext().TraceID()) + log := s.log.FromContext(ctx).New(ns.Key, ns.Value, "function", "remoteSettingService.List", "traceId", span.SpanContext().TraceID()) startTime := time.Now() var status string defer func() { duration := time.Since(startTime).Seconds() - m.metrics.listDuration.WithLabelValues(status).Observe(duration) + s.metrics.listDuration.WithLabelValues(status).Observe(duration) }() selector, err := metav1.LabelSelectorAsSelector(&labelSelector) @@ -216,64 +219,142 @@ func (m *remoteSettingService) List(ctx context.Context, labelSelector metav1.La log.Debug("empty selector. Fetching all settings") } - var allSettings []*Setting + // Pre-allocate with estimated capacity + allSettings := make([]*Setting, 0, s.pageSize*8) var continueToken string hasNext := true totalPages := 0 // Using an upper limit to prevent infinite loops for hasNext && totalPages < 1000 { totalPages++ - opts := metav1.ListOptions{ - Limit: m.pageSize, - Continue: continueToken, - } - if !selector.Empty() { - opts.LabelSelector = selector.String() - } - settingsList, lErr := m.dynamicClient.Resource(settingGroupVersion).Namespace(namespace).List(ctx, opts) + settings, nextToken, lErr := s.fetchPage(ctx, namespace, selector.String(), continueToken) if lErr != nil { status = "error" return nil, tracing.Error(span, lErr) } - for i := range settingsList.Items { - setting, pErr := parseSettingResource(&settingsList.Items[i]) - if pErr != nil { - status = "error" - return nil, tracing.Error(span, pErr) - } - allSettings = append(allSettings, setting) - } - continueToken = settingsList.GetContinue() + + allSettings = append(allSettings, settings...) + continueToken = nextToken if continueToken == "" { hasNext = false } } status = "success" - m.metrics.listResultSize.WithLabelValues(status).Observe(float64(len(allSettings))) + s.metrics.listResultSize.WithLabelValues(status).Observe(float64(len(allSettings))) return allSettings, nil } -func parseSettingResource(setting *unstructured.Unstructured) (*Setting, error) { - spec, found, err := unstructured.NestedMap(setting.Object, "spec") +func (s *remoteSettingService) fetchPage(ctx context.Context, namespace, labelSelector, continueToken string) ([]*Setting, string, error) { + req := s.restClient.Get(). + Resource(resource). + Namespace(namespace). + Param("limit", fmt.Sprintf("%d", s.pageSize)) + + if labelSelector != "" { + req = req.Param("labelSelector", labelSelector) + } + if continueToken != "" { + req = req.Param("continue", continueToken) + } + + stream, err := req.Stream(ctx) if err != nil { - return nil, fmt.Errorf("failed to get spec from setting: %w", err) - } - if !found { - return nil, fmt.Errorf("spec not found in setting %s", setting.GetName()) + return nil, "", fmt.Errorf("request failed: %w", err) } + defer func() { _ = stream.Close() }() - var result Setting - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(spec, &result); err != nil { - return nil, fmt.Errorf("failed to convert spec to Setting: %w", err) - } - - return &result, nil + return parseSettingList(stream) } -func (m *remoteSettingService) toIni(settings []*Setting) (*ini.File, error) { +// parseSettingList parses a SettingList JSON response using token-by-token streaming. +func parseSettingList(r io.Reader) ([]*Setting, string, error) { + decoder := json.NewDecoder(r) + // Currently, first page may have a large number of items. + settings := make([]*Setting, 0, 1600) + var continueToken string + + // Skip to the start of the object + if _, err := decoder.Token(); err != nil { + return nil, "", fmt.Errorf("expected start of object: %w", err) + } + + for decoder.More() { + // Read field name + tok, err := decoder.Token() + if err != nil { + return nil, "", fmt.Errorf("failed to read field name: %w", err) + } + + fieldName, ok := tok.(string) + if !ok { + continue + } + + switch fieldName { + case "metadata": + var meta settingListMetadata + if err := decoder.Decode(&meta); err != nil { + return nil, "", fmt.Errorf("failed to decode metadata: %w", err) + } + continueToken = meta.Continue + + case "items": + // Parse items array token-by-token + itemSettings, err := parseItems(decoder) + if err != nil { + return nil, "", err + } + settings = append(settings, itemSettings...) + + default: + // Skip unknown fields + var skip json.RawMessage + if err := decoder.Decode(&skip); err != nil { + return nil, "", fmt.Errorf("failed to skip field %s: %w", fieldName, err) + } + } + } + + return settings, continueToken, nil +} + +func parseItems(decoder *json.Decoder) ([]*Setting, error) { + // Expect start of array + tok, err := decoder.Token() + if err != nil { + return nil, fmt.Errorf("expected start of items array: %w", err) + } + if tok != json.Delim('[') { + return nil, fmt.Errorf("expected '[', got %v", tok) + } + + settings := make([]*Setting, 0, DefaultPageSize) + + // Parse each item + for decoder.More() { + var item settingResource + if err := decoder.Decode(&item); err != nil { + return nil, fmt.Errorf("failed to decode setting item: %w", err) + } + settings = append(settings, &Setting{ + Section: item.Spec.Section, + Key: item.Spec.Key, + Value: item.Spec.Value, + }) + } + + // Consume end of array + if _, err := decoder.Token(); err != nil { + return nil, fmt.Errorf("expected end of items array: %w", err) + } + + return settings, nil +} + +func toIni(settings []*Setting) (*ini.File, error) { conf := ini.Empty() for _, setting := range settings { if !conf.HasSection(setting.Section) { @@ -287,7 +368,7 @@ func (m *remoteSettingService) toIni(settings []*Setting) (*ini.File, error) { return conf, nil } -func getDynamicClient(config Config, log logging.Logger) (dynamic.Interface, error) { +func getRestClient(config Config, log logging.Logger) (*rest.RESTClient, error) { if config.URL == "" { return nil, fmt.Errorf("URL cannot be empty") } @@ -296,7 +377,7 @@ func getDynamicClient(config Config, log logging.Logger) (dynamic.Interface, err } wrapTransport := config.WrapTransport - if config.WrapTransport == nil { + if wrapTransport == nil { log.Debug("using default wrapTransport with TokenExchangeClient") wrapTransport = func(rt http.RoundTripper) http.RoundTripper { return &authRoundTripper{ @@ -316,13 +397,21 @@ func getDynamicClient(config Config, log logging.Logger) (dynamic.Interface, err burst = config.Burst } - return dynamic.NewForConfig(&clientrest.Config{ + restConfig := &rest.Config{ Host: config.URL, - WrapTransport: wrapTransport, TLSClientConfig: config.TLSClientConfig, + WrapTransport: wrapTransport, QPS: qps, Burst: burst, - }) + // Configure for our API group + APIPath: "/apis", + ContentConfig: rest.ContentConfig{ + GroupVersion: &settingGroupVersion, + NegotiatedSerializer: serializer.NewCodecFactory(nil).WithoutConversion(), + }, + } + + return rest.RESTClientFor(restConfig) } // authRoundTripper wraps an HTTP transport with token-based authentication. @@ -341,10 +430,9 @@ func (a *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) if err != nil { return nil, fmt.Errorf("failed to exchange token: %w", err) } - req = utilnet.CloneRequest(req) - - req.Header.Set("X-Access-Token", fmt.Sprintf("Bearer %s", token.Token)) - return a.transport.RoundTrip(req) + reqCopy := req.Clone(req.Context()) + reqCopy.Header.Set("X-Access-Token", fmt.Sprintf("Bearer %s", token.Token)) + return a.transport.RoundTrip(reqCopy) } func initMetrics() remoteSettingServiceMetrics { @@ -373,12 +461,12 @@ func initMetrics() remoteSettingServiceMetrics { return metrics } -func (m *remoteSettingService) Describe(descs chan<- *prometheus.Desc) { - m.metrics.listDuration.Describe(descs) - m.metrics.listResultSize.Describe(descs) +func (s *remoteSettingService) Describe(descs chan<- *prometheus.Desc) { + s.metrics.listDuration.Describe(descs) + s.metrics.listResultSize.Describe(descs) } -func (m *remoteSettingService) Collect(metrics chan<- prometheus.Metric) { - m.metrics.listDuration.Collect(metrics) - m.metrics.listResultSize.Collect(metrics) +func (s *remoteSettingService) Collect(metrics chan<- prometheus.Metric) { + s.metrics.listDuration.Collect(metrics) + s.metrics.listResultSize.Collect(metrics) } diff --git a/pkg/services/setting/service_test.go b/pkg/services/setting/service_test.go index 949ccc73a86..5d9a2565551 100644 --- a/pkg/services/setting/service_test.go +++ b/pkg/services/setting/service_test.go @@ -1,69 +1,36 @@ package setting import ( + "bytes" "context" "fmt" "net/http" + "net/http/httptest" + "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/client-go/dynamic/fake" - k8testing "k8s.io/client-go/testing" - - authlib "github.com/grafana/authlib/authn" - "github.com/grafana/grafana/pkg/infra/log" ) func TestRemoteSettingService_ListAsIni(t *testing.T) { - t.Run("should filter settings by label selector", func(t *testing.T) { - // Create multiple settings, only some matching the selector - setting1 := newUnstructuredSetting("test-namespace", Setting{Section: "database", Key: "type", Value: "postgres"}) - setting2 := newUnstructuredSetting("test-namespace", Setting{Section: "server", Key: "port", Value: "3000"}) - setting3 := newUnstructuredSetting("test-namespace", Setting{Section: "database", Key: "host", Value: "localhost"}) - - client := newTestClient(500, setting1, setting2, setting3) - - // Create a selector that should match only database settings - selector := metav1.LabelSelector{ - MatchLabels: map[string]string{ - "section": "database", - }, - } - - ctx := request.WithNamespace(context.Background(), "test-namespace") - result, err := client.ListAsIni(ctx, selector) - - require.NoError(t, err) - assert.NotNil(t, result) - // Should only have database settings, not server settings - assert.True(t, result.HasSection("database")) - assert.Equal(t, "postgres", result.Section("database").Key("type").String()) - assert.Equal(t, "localhost", result.Section("database").Key("host").String()) - // Should NOT have server settings - assert.False(t, result.HasSection("server")) - }) - t.Run("should return all settings with empty selector", func(t *testing.T) { - // Create multiple settings across different sections - setting1 := newUnstructuredSetting("test-namespace", Setting{Section: "server", Key: "port", Value: "3000"}) - setting2 := newUnstructuredSetting("test-namespace", Setting{Section: "database", Key: "type", Value: "mysql"}) - - client := newTestClient(500, setting1, setting2) - - // Empty selector should select everything - selector := metav1.LabelSelector{} + settings := []Setting{ + {Section: "server", Key: "port", Value: "3000"}, + {Section: "database", Key: "type", Value: "mysql"}, + } + server := newTestServer(t, settings, "") + defer server.Close() + client := newTestClient(t, server.URL, 500) ctx := request.WithNamespace(context.Background(), "test-namespace") - result, err := client.ListAsIni(ctx, selector) + + result, err := client.ListAsIni(ctx, metav1.LabelSelector{}) require.NoError(t, err) assert.NotNil(t, result) - // Should have all settings from all sections assert.True(t, result.HasSection("server")) assert.Equal(t, "3000", result.Section("server").Key("port").String()) assert.True(t, result.HasSection("database")) @@ -73,209 +40,168 @@ func TestRemoteSettingService_ListAsIni(t *testing.T) { func TestRemoteSettingService_List(t *testing.T) { t.Run("should handle single page response", func(t *testing.T) { - setting := newUnstructuredSetting("test-namespace", Setting{Section: "server", Key: "port", Value: "3000"}) - - client := newTestClient(500, setting) + settings := []Setting{ + {Section: "server", Key: "port", Value: "3000"}, + } + server := newTestServer(t, settings, "") + defer server.Close() + client := newTestClient(t, server.URL, 500) ctx := request.WithNamespace(context.Background(), "test-namespace") + result, err := client.List(ctx, metav1.LabelSelector{}) require.NoError(t, err) assert.Len(t, result, 1) - - spec := result[0] - assert.Equal(t, "server", spec.Section) - assert.Equal(t, "port", spec.Key) - assert.Equal(t, "3000", spec.Value) + assert.Equal(t, "server", result[0].Section) + assert.Equal(t, "port", result[0].Key) + assert.Equal(t, "3000", result[0].Value) }) - t.Run("should handle multiple pages", func(t *testing.T) { - totalPages := 3 - pageSize := 5 - - pages := make([][]*unstructured.Unstructured, totalPages) - for pageNum := 0; pageNum < totalPages; pageNum++ { - for idx := 0; idx < pageSize; idx++ { - item := newUnstructuredSetting( - "test-namespace", - Setting{ - Section: fmt.Sprintf("section-%d", pageNum), - Key: fmt.Sprintf("key-%d", idx), - Value: fmt.Sprintf("val-%d-%d", pageNum, idx), - }, - ) - pages[pageNum] = append(pages[pageNum], item) - } - } - - scheme := runtime.NewScheme() - dynamicClient := fake.NewSimpleDynamicClientWithCustomListKinds(scheme, settingGroupListKind) - listCallCount := 0 - dynamicClient.PrependReactor("list", "settings", func(action k8testing.Action) (handled bool, ret runtime.Object, err error) { - listCallCount++ - - continueToken := fmt.Sprintf("continue-%d", listCallCount) - if listCallCount == totalPages { - continueToken = "" - } - - if listCallCount <= totalPages { - list := &unstructured.UnstructuredList{ - Object: map[string]interface{}{ - "apiVersion": ApiGroup + "/" + apiVersion, - "kind": listKind, - }, - } - list.SetContinue(continueToken) - for _, item := range pages[listCallCount-1] { - list.Items = append(list.Items, *item) - } - return true, list, nil - } - - return false, nil, nil - }) - - client := &remoteSettingService{ - dynamicClient: dynamicClient, - pageSize: int64(pageSize), - log: log.NewNopLogger(), - metrics: initMetrics(), + t.Run("should handle multiple settings", func(t *testing.T) { + settings := []Setting{ + {Section: "server", Key: "port", Value: "3000"}, + {Section: "database", Key: "host", Value: "localhost"}, + {Section: "database", Key: "port", Value: "5432"}, } + server := newTestServer(t, settings, "") + defer server.Close() + client := newTestClient(t, server.URL, 500) ctx := request.WithNamespace(context.Background(), "test-namespace") + result, err := client.List(ctx, metav1.LabelSelector{}) require.NoError(t, err) - assert.Len(t, result, totalPages*pageSize) - assert.Equal(t, totalPages, listCallCount) + assert.Len(t, result, 3) }) - t.Run("should pass label selector when provided", func(t *testing.T) { - scheme := runtime.NewScheme() - dynamicClient := fake.NewSimpleDynamicClientWithCustomListKinds(scheme, settingGroupListKind) - dynamicClient.PrependReactor("list", "settings", func(action k8testing.Action) (handled bool, ret runtime.Object, err error) { - listAction := action.(k8testing.ListActionImpl) - assert.Equal(t, "app=grafana", listAction.ListOptions.LabelSelector) - return true, &unstructured.UnstructuredList{}, nil - }) - - client := &remoteSettingService{ - dynamicClient: dynamicClient, - pageSize: 500, - log: log.NewNopLogger(), - metrics: initMetrics(), + t.Run("should handle pagination with continue token", func(t *testing.T) { + // First page + page1Settings := []Setting{ + {Section: "section-0", Key: "key-0", Value: "value-0"}, + {Section: "section-0", Key: "key-1", Value: "value-1"}, + } + // Second page + page2Settings := []Setting{ + {Section: "section-1", Key: "key-0", Value: "value-2"}, + {Section: "section-1", Key: "key-1", Value: "value-3"}, } + requestCount := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requestCount++ + continueToken := r.URL.Query().Get("continue") + + var settings []Setting + var nextContinue string + + if continueToken == "" { + settings = page1Settings + nextContinue = "page2" + } else { + settings = page2Settings + nextContinue = "" + } + + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(generateSettingsJSON(settings, nextContinue))) + })) + defer server.Close() + + client := newTestClient(t, server.URL, 2) ctx := request.WithNamespace(context.Background(), "test-namespace") - _, err := client.List(ctx, metav1.LabelSelector{MatchLabels: map[string]string{"app": "grafana"}}) + + result, err := client.List(ctx, metav1.LabelSelector{}) require.NoError(t, err) + assert.Len(t, result, 4) + assert.Equal(t, 2, requestCount) }) - t.Run("should stop pagination at 1000 pages", func(t *testing.T) { - scheme := runtime.NewScheme() - dynamicClient := fake.NewSimpleDynamicClientWithCustomListKinds(scheme, settingGroupListKind) - listCallCount := 0 - dynamicClient.PrependReactor("list", "settings", func(action k8testing.Action) (handled bool, ret runtime.Object, err error) { - listCallCount++ - // Always return a continue token to simulate infinite pagination - list := &unstructured.UnstructuredList{} - list.SetContinue("continue-forever") - return true, list, nil - }) + t.Run("should return error when namespace is missing", func(t *testing.T) { + server := newTestServer(t, nil, "") + defer server.Close() - client := &remoteSettingService{ - dynamicClient: dynamicClient, - pageSize: 10, - log: log.NewNopLogger(), - metrics: initMetrics(), - } + client := newTestClient(t, server.URL, 500) + ctx := context.Background() // No namespace - ctx := request.WithNamespace(context.Background(), "test-namespace") - _, err := client.List(ctx, metav1.LabelSelector{}) - - require.NoError(t, err) - assert.Equal(t, 1000, listCallCount, "Should stop at 1000 pages to prevent infinite loops") - }) - - t.Run("should return error when parsing setting fails", func(t *testing.T) { - scheme := runtime.NewScheme() - dynamicClient := fake.NewSimpleDynamicClientWithCustomListKinds(scheme, settingGroupListKind) - dynamicClient.PrependReactor("list", "settings", func(action k8testing.Action) (handled bool, ret runtime.Object, err error) { - // Return a malformed setting without spec - list := &unstructured.UnstructuredList{ - Object: map[string]interface{}{ - "apiVersion": ApiGroup + "/" + apiVersion, - "kind": listKind, - }, - } - malformedSetting := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": ApiGroup + "/" + apiVersion, - "kind": kind, - "metadata": map[string]interface{}{ - "name": "malformed", - "namespace": "test-namespace", - }, - // Missing spec - }, - } - list.Items = append(list.Items, *malformedSetting) - return true, list, nil - }) - - client := &remoteSettingService{ - dynamicClient: dynamicClient, - pageSize: 500, - log: log.NewNopLogger(), - metrics: initMetrics(), - } - - ctx := request.WithNamespace(context.Background(), "test-namespace") result, err := client.List(ctx, metav1.LabelSelector{}) require.Error(t, err) assert.Nil(t, result) - assert.Contains(t, err.Error(), "spec not found") - }) -} - -func TestParseSettingResource(t *testing.T) { - t.Run("should parse valid setting resource", func(t *testing.T) { - setting := newUnstructuredSetting("test-namespace", Setting{Section: "database", Key: "type", Value: "postgres"}) - - result, err := parseSettingResource(setting) - - require.NoError(t, err) - assert.NotNil(t, result) - assert.Equal(t, "database", result.Section) - assert.Equal(t, "type", result.Key) - assert.Equal(t, "postgres", result.Value) + assert.Contains(t, err.Error(), "missing namespace") }) - t.Run("should return error when spec is missing", func(t *testing.T) { - setting := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": ApiGroup + "/" + apiVersion, - "kind": kind, - "metadata": map[string]interface{}{ - "name": "test-setting", - "namespace": "test-namespace", - }, - // No spec - }, - } + t.Run("should return error on HTTP error", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte("internal server error")) + })) + defer server.Close() - result, err := parseSettingResource(setting) + client := newTestClient(t, server.URL, 500) + ctx := request.WithNamespace(context.Background(), "test-namespace") + + result, err := client.List(ctx, metav1.LabelSelector{}) require.Error(t, err) assert.Nil(t, result) - assert.Contains(t, err.Error(), "spec not found") }) } -func TestRemoteSettingService_ToIni(t *testing.T) { +func TestParseSettingList(t *testing.T) { + t.Run("should parse valid settings list", func(t *testing.T) { + jsonData := `{ + "apiVersion": "setting.grafana.app/v0alpha1", + "kind": "SettingList", + "metadata": {"continue": ""}, + "items": [ + {"spec": {"section": "database", "key": "type", "value": "postgres"}}, + {"spec": {"section": "server", "key": "port", "value": "3000"}} + ] + }` + + settings, continueToken, err := parseSettingList(strings.NewReader(jsonData)) + + require.NoError(t, err) + assert.Len(t, settings, 2) + assert.Equal(t, "", continueToken) + assert.Equal(t, "database", settings[0].Section) + assert.Equal(t, "type", settings[0].Key) + assert.Equal(t, "postgres", settings[0].Value) + }) + + t.Run("should parse continue token", func(t *testing.T) { + jsonData := `{ + "apiVersion": "setting.grafana.app/v0alpha1", + "kind": "SettingList", + "metadata": {"continue": "next-page-token"}, + "items": [] + }` + + _, continueToken, err := parseSettingList(strings.NewReader(jsonData)) + + require.NoError(t, err) + assert.Equal(t, "next-page-token", continueToken) + }) + + t.Run("should handle empty items", func(t *testing.T) { + jsonData := `{ + "apiVersion": "setting.grafana.app/v0alpha1", + "kind": "SettingList", + "metadata": {}, + "items": [] + }` + + settings, _, err := parseSettingList(strings.NewReader(jsonData)) + + require.NoError(t, err) + assert.Len(t, settings, 0) + }) +} + +func TestToIni(t *testing.T) { t.Run("should convert settings to ini format", func(t *testing.T) { settings := []*Setting{ {Section: "database", Key: "type", Value: "postgres"}, @@ -283,12 +209,7 @@ func TestRemoteSettingService_ToIni(t *testing.T) { {Section: "server", Key: "http_port", Value: "3000"}, } - client := &remoteSettingService{ - pageSize: 500, - log: log.NewNopLogger(), - } - - result, err := client.toIni(settings) + result, err := toIni(settings) require.NoError(t, err) assert.NotNil(t, result) @@ -302,12 +223,7 @@ func TestRemoteSettingService_ToIni(t *testing.T) { t.Run("should handle empty settings list", func(t *testing.T) { var settings []*Setting - client := &remoteSettingService{ - pageSize: 500, - log: log.NewNopLogger(), - } - - result, err := client.toIni(settings) + result, err := toIni(settings) require.NoError(t, err) assert.NotNil(t, result) @@ -315,35 +231,13 @@ func TestRemoteSettingService_ToIni(t *testing.T) { assert.Len(t, sections, 1) // Only default section }) - t.Run("should create section if it does not exist", func(t *testing.T) { - settings := []*Setting{ - {Section: "new_section", Key: "new_key", Value: "new_value"}, - } - - client := &remoteSettingService{ - pageSize: 500, - log: log.NewNopLogger(), - } - - result, err := client.toIni(settings) - - require.NoError(t, err) - assert.True(t, result.HasSection("new_section")) - assert.Equal(t, "new_value", result.Section("new_section").Key("new_key").String()) - }) - t.Run("should handle multiple keys in same section", func(t *testing.T) { settings := []*Setting{ {Section: "auth", Key: "disable_login_form", Value: "false"}, {Section: "auth", Key: "disable_signout_menu", Value: "true"}, } - client := &remoteSettingService{ - pageSize: 500, - log: log.NewNopLogger(), - } - - result, err := client.toIni(settings) + result, err := toIni(settings) require.NoError(t, err) assert.True(t, result.HasSection("auth")) @@ -383,24 +277,23 @@ func TestNew(t *testing.T) { assert.Equal(t, int64(100), remoteClient.pageSize) }) - t.Run("should use default page size when zero is provided", func(t *testing.T) { + t.Run("should create client with custom QPS and Burst", func(t *testing.T) { config := Config{ URL: "https://example.com", WrapTransport: func(rt http.RoundTripper) http.RoundTripper { return rt }, - PageSize: 0, + QPS: 50.0, + Burst: 100, } client, err := New(config) require.NoError(t, err) assert.NotNil(t, client) - remoteClient := client.(*remoteSettingService) - assert.Equal(t, DefaultPageSize, remoteClient.pageSize) }) - t.Run("should return error when config is invalid", func(t *testing.T) { + t.Run("should return error when URL is empty", func(t *testing.T) { config := Config{ - URL: "", // Invalid: empty URL + URL: "", } client, err := New(config) @@ -409,134 +302,126 @@ func TestNew(t *testing.T) { assert.Nil(t, client) assert.Contains(t, err.Error(), "URL cannot be empty") }) -} -func TestGetDynamicClient(t *testing.T) { - logger := log.NewNopLogger() - - t.Run("should return error when SettingServiceURL is empty", func(t *testing.T) { - config := Config{ - URL: "", - WrapTransport: func(rt http.RoundTripper) http.RoundTripper { return rt }, - } - - client, err := getDynamicClient(config, logger) - - require.Error(t, err) - assert.Nil(t, client) - assert.Contains(t, err.Error(), "URL cannot be empty") - }) - - t.Run("should return error when both TokenExchangeClient and WrapTransport are nil", func(t *testing.T) { + t.Run("should return error when auth is not configured", func(t *testing.T) { config := Config{ URL: "https://example.com", TokenExchangeClient: nil, WrapTransport: nil, } - client, err := getDynamicClient(config, logger) + client, err := New(config) require.Error(t, err) assert.Nil(t, client) assert.Contains(t, err.Error(), "must set either TokenExchangeClient or WrapTransport") }) - t.Run("should create client with WrapTransport", func(t *testing.T) { - config := Config{ - URL: "https://example.com", - WrapTransport: func(rt http.RoundTripper) http.RoundTripper { return rt }, - } - - client, err := getDynamicClient(config, logger) - - require.NoError(t, err) - assert.NotNil(t, client) - }) - - t.Run("should not fail when QPS and Burst are not provided", func(t *testing.T) { - config := Config{ - URL: "https://example.com", - WrapTransport: func(rt http.RoundTripper) http.RoundTripper { return rt }, - } - - client, err := getDynamicClient(config, logger) - - require.NoError(t, err) - assert.NotNil(t, client) - }) - - t.Run("should not fail when custom QPS and Burst are provided", func(t *testing.T) { - config := Config{ - URL: "https://example.com", - WrapTransport: func(rt http.RoundTripper) http.RoundTripper { return rt }, - QPS: 10.0, - Burst: 20, - } - - client, err := getDynamicClient(config, logger) - - require.NoError(t, err) - assert.NotNil(t, client) - }) - - t.Run("should use WrapTransport when both WrapTransport and TokenExchangeClient are provided", func(t *testing.T) { + t.Run("should use WrapTransport when provided", func(t *testing.T) { wrapTransportCalled := false - tokenExchangeClient := &authlib.TokenExchangeClient{} config := Config{ - URL: "https://example.com", - TokenExchangeClient: tokenExchangeClient, + URL: "https://example.com", WrapTransport: func(rt http.RoundTripper) http.RoundTripper { wrapTransportCalled = true return rt }, } - client, err := getDynamicClient(config, logger) + client, err := New(config) require.NoError(t, err) assert.NotNil(t, client) - assert.True(t, wrapTransportCalled, "WrapTransport should be called and take precedence over TokenExchangeClient") + assert.True(t, wrapTransportCalled) }) } -// Helper function to create an unstructured Setting object for tests -func newUnstructuredSetting(namespace string, spec Setting) *unstructured.Unstructured { - // Generate resource name in the format {section}--{key} - name := fmt.Sprintf("%s--%s", spec.Section, spec.Key) +// Helper functions - obj := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": ApiGroup + "/" + apiVersion, - "kind": kind, - "metadata": map[string]interface{}{ - "name": name, - "namespace": namespace, - }, - "spec": map[string]interface{}{ - "section": spec.Section, - "key": spec.Key, - "value": spec.Value, - }, - }, - } - // Always set section and key labels - obj.SetLabels(map[string]string{ - "section": spec.Section, - "key": spec.Key, - }) - return obj +func newTestServer(t *testing.T, settings []Setting, continueToken string) *httptest.Server { + t.Helper() + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(generateSettingsJSON(settings, continueToken))) + })) } -// Helper function to create a test client with the dynamic fake client -func newTestClient(pageSize int64, objects ...runtime.Object) *remoteSettingService { - scheme := runtime.NewScheme() - dynamicClient := fake.NewSimpleDynamicClientWithCustomListKinds(scheme, settingGroupListKind, objects...) +func newTestClient(t *testing.T, serverURL string, pageSize int64) Service { + t.Helper() + config := Config{ + URL: serverURL, + WrapTransport: func(rt http.RoundTripper) http.RoundTripper { return rt }, + PageSize: pageSize, + } + client, err := New(config) + require.NoError(t, err) + return client +} - return &remoteSettingService{ - dynamicClient: dynamicClient, - pageSize: pageSize, - log: log.NewNopLogger(), - metrics: initMetrics(), +func generateSettingsJSON(settings []Setting, continueToken string) string { + var sb strings.Builder + sb.WriteString(fmt.Sprintf(`{"apiVersion":"setting.grafana.app/v0alpha1","kind":"SettingList","metadata":{"continue":"%s"},"items":[`, continueToken)) + + for i, s := range settings { + if i > 0 { + sb.WriteString(",") + } + sb.WriteString(fmt.Sprintf( + `{"apiVersion":"setting.grafana.app/v0alpha1","kind":"Setting","metadata":{"name":"%s--%s","namespace":"test-namespace"},"spec":{"section":"%s","key":"%s","value":"%s"}}`, + s.Section, s.Key, s.Section, s.Key, s.Value, + )) + } + + sb.WriteString(`]}`) + return sb.String() +} + +// Benchmark tests for streaming JSON parser + +func BenchmarkParseSettingList(b *testing.B) { + jsonData := generateSettingListJSON(4000, 100) + jsonBytes := []byte(jsonData) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + reader := bytes.NewReader(jsonBytes) + _, _, _ = parseSettingList(reader) } } + +func BenchmarkParseSettingList_SinglePage(b *testing.B) { + jsonData := generateSettingListJSON(500, 50) + jsonBytes := []byte(jsonData) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + reader := bytes.NewReader(jsonBytes) + _, _, _ = parseSettingList(reader) + } +} + +// generateSettingListJSON generates a K8s-style SettingList JSON response for benchmarks +func generateSettingListJSON(totalSettings, numSections int) string { + var sb strings.Builder + sb.WriteString(`{"apiVersion":"setting.grafana.app/v0alpha1","kind":"SettingList","metadata":{"continue":""},"items":[`) + + settingsPerSection := totalSettings / numSections + first := true + for section := 0; section < numSections; section++ { + for key := 0; key < settingsPerSection; key++ { + if !first { + sb.WriteString(",") + } + first = false + sb.WriteString(fmt.Sprintf( + `{"apiVersion":"setting.grafana.app/v0alpha1","kind":"Setting","metadata":{"name":"section-%03d--key-%03d","namespace":"bench-ns"},"spec":{"section":"section-%03d","key":"key-%03d","value":"value-for-section-%d-key-%d"}}`, + section, key, section, key, section, key, + )) + } + } + + sb.WriteString(`]}`) + return sb.String() +} From bc88796e6e879dc4e2505d18115cf06ba61406e7 Mon Sep 17 00:00:00 2001 From: Usman Ahmad Date: Thu, 11 Dec 2025 16:42:09 +0100 Subject: [PATCH 024/297] Created Troubleshooting guide for MySQL data source plugin (#114737) * created troubleshooting guide for mysql data source plugin Signed-off-by: Usman Ahmad * Apply suggestions from code review thanks for the code review Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> * rename file from _index.md to index.md Signed-off-by: Usman Ahmad * Update docs/sources/datasources/mysql/troubleshoot/index.md --------- Signed-off-by: Usman Ahmad Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> --- .../datasources/mysql/troubleshoot/index.md | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 docs/sources/datasources/mysql/troubleshoot/index.md diff --git a/docs/sources/datasources/mysql/troubleshoot/index.md b/docs/sources/datasources/mysql/troubleshoot/index.md new file mode 100644 index 00000000000..0fcf80e55ac --- /dev/null +++ b/docs/sources/datasources/mysql/troubleshoot/index.md @@ -0,0 +1,80 @@ +--- +description: Learn how to troubleshoot common problems with the Grafana MySQL data source plugin +keywords: + - grafana + - mysql + - query +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Troubleshoot +title: Troubleshoot common problems with the Grafana MySQL data source plugin +weight: 40 +refs: + variables: + - pattern: /docs/grafana/ + destination: /docs/grafana//dashboards/variables/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/visualizations/dashboards/variables/ + variable-syntax-advanced-variable-format-options: + - pattern: /docs/grafana/ + destination: /docs/grafana//dashboards/variables/variable-syntax/#advanced-variable-format-options + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/visualizations/dashboards/variables/variable-syntax/#advanced-variable-format-options + annotate-visualizations: + - pattern: /docs/grafana/ + destination: /docs/grafana//dashboards/build-dashboards/annotate-visualizations/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/visualizations/dashboards/build-dashboards/annotate-visualizations/ + explore: + - pattern: /docs/grafana/ + destination: /docs/grafana//explore/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//explore/ + query-transform-data: + - pattern: /docs/grafana/ + destination: /docs/grafana//panels-visualizations/query-transform-data/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/ + panel-inspector: + - pattern: /docs/grafana/ + destination: /docs/grafana//panels-visualizations/panel-inspector/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/visualizations/panels-visualizations/panel-inspector/ + query-editor: + - pattern: /docs/grafana/ + destination: /docs/grafana//panels-visualizations/query-transform-data/#query-editors + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/#query-editors + alert-rules: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/fundamentals/alert-rules/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/ + template-annotations-and-labels: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/alerting-rules/templates/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/templates/ + configure-standard-options: + - pattern: /docs/grafana/ + - destination: /docs/grafana//panels-visualizations/configure-standard-options/ +--- + +# Troubleshoot common problems with the Grafana MySQL data source plugin + +This page lists common issues you might experience when setting up the Grafana MySQL data source plugin. + +### My data source connection fails when using the Grafana MySQL data source plugin + +- Check if the MySQL server is up and running. +- Make sure that your firewall is open for MySQL server (default port is `3306`). +- Ensure that you have the correct permissions to access the MySQL server and also have permission to access the database. +- If the error persists, create a new user for the Grafana MySQL data source plugin with correct permissions and try to connect with it. + +### What should I do if I see "An unexpected error happened" or "Could not connect to MySQL" after trying all of the above? + +- Check the Grafana logs for more details about the error. +- For Grafana Cloud customers, contact support. From 5bda17be3f21514e6ea04fbaf57e0622757d031c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Jim=C3=A9nez=20S=C3=A1nchez?= Date: Thu, 11 Dec 2025 17:08:57 +0100 Subject: [PATCH 025/297] Provisioning: Update provisioning docs to reflect kubernetesDashboards defaults to true (#115159) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docs: Update provisioning docs to reflect kubernetesDashboards defaults to true The kubernetesDashboards feature toggle now defaults to true, so users don't need to explicitly enable it in their configuration. Updated documentation and UI to reflect this: - Removed kubernetesDashboards from configuration examples - Added notes explaining it's enabled by default - Clarified that users only need to take action if they've explicitly disabled it - Kept validation checks to catch explicit disables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.5 --- .../provision-resources/file-path-setup.md | 7 +++---- .../provision-resources/git-sync-setup.md | 7 +++---- .../provisioning/GettingStarted/GettingStarted.tsx | 3 +-- public/locales/en-US/grafana.json | 2 +- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/sources/as-code/observability-as-code/provision-resources/file-path-setup.md b/docs/sources/as-code/observability-as-code/provision-resources/file-path-setup.md index f2cd24a46b1..d071ccca0de 100644 --- a/docs/sources/as-code/observability-as-code/provision-resources/file-path-setup.md +++ b/docs/sources/as-code/observability-as-code/provision-resources/file-path-setup.md @@ -54,7 +54,7 @@ For production systems, use the `folderFromFilesStructure` capability instead of ## Before you begin {{< admonition type="note" >}} -Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana to use this feature. +Enable the `provisioning` feature toggle in Grafana to use this feature. {{< /admonition >}} To set up file provisioning, you need: @@ -67,7 +67,7 @@ To set up file provisioning, you need: ## Enable required feature toggles and configure permitted paths -To activate local file provisioning in Grafana, you need to enable the `provisioning` and `kubernetesDashboards` feature toggles. +To activate local file provisioning in Grafana, you need to enable the `provisioning` feature toggle. For additional information about feature toggles, refer to [Configure feature toggles](https://grafana.com/docs/grafana//setup-grafana/configure-grafana/feature-toggles). The local setting must be a relative path and its relative path must be configured in the `permitted_provisioned_paths` configuration option. @@ -82,12 +82,11 @@ Any subdirectories are automatically included. The values that you enter for the `permitted_provisioning_paths` become the base paths for those entered when you enter a local path in the **Connect to local storage** wizard. 1. Open your Grafana configuration file, either `grafana.ini` or `custom.ini`. For file location based on operating system, refer to [Configuration file location](https://grafana.com/docs/grafana//setup-grafana/configure-grafana/feature-toggles/#experimental-feature-toggles). -1. Locate or add a `[feature_toggles]` section. Add these values: +1. Locate or add a `[feature_toggles]` section. Add this value: ```ini [feature_toggles] provisioning = true - kubernetesDashboards = true ; use k8s from browser ``` 1. Locate or add a `[paths]` section. To add more than one location, use the pipe character (`|`) to separate the paths. The list should not include empty paths or trailing pipes. Add these values: diff --git a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md index 758226c6257..553027f4132 100644 --- a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md +++ b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md @@ -74,17 +74,16 @@ Alternatively, you can configure a local file system instead of using GitHub. Re ## Enable required feature toggles -To activate Git Sync in Grafana, you need to enable the `provisioning` and `kubernetesDashboards` feature toggles. For more information about feature toggles, refer to [Configure feature toggles](https://grafana.com/docs/grafana//setup-grafana/configure-grafana/feature-toggles/#experimental-feature-toggles). +To activate Git Sync in Grafana, you need to enable the `provisioning` feature toggle. For more information about feature toggles, refer to [Configure feature toggles](https://grafana.com/docs/grafana//setup-grafana/configure-grafana/feature-toggles/#experimental-feature-toggles). -To enable the required feature toggles: +To enable the required feature toggle: 1. Open your Grafana configuration file, either `grafana.ini` or `custom.ini`. For file location based on operating system, refer to [Configuration file location](https://grafana.com/docs/grafana//setup-grafana/configure-grafana/feature-toggles/#experimental-feature-toggles). -1. Locate or add a `[feature_toggles]` section. Add these values: +1. Locate or add a `[feature_toggles]` section. Add this value: ```ini [feature_toggles] provisioning = true - kubernetesDashboards = true ; use k8s from browser ``` 1. Save the changes to the file and restart Grafana. diff --git a/public/app/features/provisioning/GettingStarted/GettingStarted.tsx b/public/app/features/provisioning/GettingStarted/GettingStarted.tsx index d38c19c9fd5..888f48a26f0 100644 --- a/public/app/features/provisioning/GettingStarted/GettingStarted.tsx +++ b/public/app/features/provisioning/GettingStarted/GettingStarted.tsx @@ -22,7 +22,6 @@ const featureIni = `# In your custom.ini file [feature_toggles] provisioning = true -kubernetesDashboards = true ; use k8s from browser `; const ngrokExample = `ngrok http 3000 @@ -103,7 +102,7 @@ const getModalContent = (setupType: SetupType) => { ), description: t( 'provisioning.getting-started.step-description-enable-feature-toggles', - 'Add these settings to your custom.ini file to enable necessary features:' + 'Add the provisioning feature toggle to your custom.ini file. Note: kubernetesDashboards is enabled by default, but if you have explicitly disabled it, you will need to enable it in your Grafana settings or remove the override from your configuration.' ), code: featureIni, }, diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index bdf7943f90e..761ed8f51ec 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -11838,7 +11838,7 @@ "modal-title-set-up-public-access": "Set up public access", "modal-title-set-up-required-features": "Set up required features", "step-description-copy-url": "From the ngrok output, copy the https:// forwarding URL that looks like this:", - "step-description-enable-feature-toggles": "Add these settings to your custom.ini file to enable necessary features:", + "step-description-enable-feature-toggles": "Add the provisioning feature toggle to your custom.ini file. Note: kubernetesDashboards is enabled by default, but if you have explicitly disabled it, you will need to enable it in your Grafana settings or remove the override from your configuration.", "step-description-start-ngrok": "Run this command to create a secure tunnel to your local Grafana:", "step-description-update-grafana-config": "Add this to your custom.ini file, replacing the URL with your actual ngrok URL:", "step-title-copy-url": "Copy your public URL", From 5dcfc190609c9d875a38974ca11fdc6398b99847 Mon Sep 17 00:00:00 2001 From: Jesse David Peterson Date: Thu, 11 Dec 2025 11:22:10 -0500 Subject: [PATCH 026/297] Table: Add title attribute to make truncated headings legible (#115155) * fix(table): add HTML title attribute to make truncated headings legible * fix(table): avoid redundant display name calculation Co-authored-by: Paul Marbach --------- Co-authored-by: Paul Marbach --- .../src/components/Table/TableNG/TableNG.test.tsx | 13 +++++++++++++ .../Table/TableNG/components/HeaderCell.tsx | 4 +++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.test.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.test.tsx index 56e38b9cd60..ead8014e550 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.test.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.test.tsx @@ -451,6 +451,19 @@ describe('TableNG', () => { expect(screen.getByText('A1')).toBeInTheDocument(); expect(screen.getByText('1')).toBeInTheDocument(); }); + + it('shows full column name in title attribute for truncated headers', () => { + const { container } = render( + + ); + + const headers = container.querySelectorAll('[role="columnheader"]'); + const firstHeaderSpan = headers[0].querySelector('span'); + const secondHeaderSpan = headers[1].querySelector('span'); + + expect(firstHeaderSpan).toHaveAttribute('title', 'Column A'); + expect(secondHeaderSpan).toHaveAttribute('title', 'Column B'); + }); }); describe('Footer options', () => { diff --git a/packages/grafana-ui/src/components/Table/TableNG/components/HeaderCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/components/HeaderCell.tsx index 8ccc73a98b9..fc59c08aa3c 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/components/HeaderCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/components/HeaderCell.tsx @@ -55,7 +55,9 @@ const HeaderCell: React.FC = ({ {showTypeIcons && ( )} - {getDisplayName(field)} + + {displayName} + {direction && ( Date: Thu, 11 Dec 2025 16:31:02 +0000 Subject: [PATCH 027/297] I18n: Download translations from Crowdin (#115123) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 14 +++++++++++--- public/locales/de-DE/grafana.json | 14 +++++++++++--- public/locales/es-ES/grafana.json | 14 +++++++++++--- public/locales/fr-FR/grafana.json | 14 +++++++++++--- public/locales/hu-HU/grafana.json | 14 +++++++++++--- public/locales/id-ID/grafana.json | 14 +++++++++++--- public/locales/it-IT/grafana.json | 14 +++++++++++--- public/locales/ja-JP/grafana.json | 14 +++++++++++--- public/locales/ko-KR/grafana.json | 14 +++++++++++--- public/locales/nl-NL/grafana.json | 14 +++++++++++--- public/locales/pl-PL/grafana.json | 14 +++++++++++--- public/locales/pt-BR/grafana.json | 14 +++++++++++--- public/locales/pt-PT/grafana.json | 14 +++++++++++--- public/locales/ru-RU/grafana.json | 14 +++++++++++--- public/locales/sv-SE/grafana.json | 14 +++++++++++--- public/locales/tr-TR/grafana.json | 14 +++++++++++--- public/locales/zh-Hans/grafana.json | 14 +++++++++++--- public/locales/zh-Hant/grafana.json | 14 +++++++++++--- 18 files changed, 198 insertions(+), 54 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index d5cd731a4ea..e38462b15df 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -8897,6 +8897,13 @@ "aria-label-default": "Vyberte barvu", "aria-label-selected-color": "{{colorLabel}} barva" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "Odstranit", "cancel": "Zrušit", @@ -9094,6 +9101,8 @@ "interactive-table": { "aria-label-collapse-all": "Sbalit všechny řádky", "aria-label-expand-all": "Rozbalit všechny řádky", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "Řádek přepnutí rozbalen", "tooltip-collapse-all": "Sbalit všechny řádky", "tooltip-expand-all": "Rozbalit všechny řádky" @@ -12529,13 +12538,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index c225d5a460a..1ef1f3f3733 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -8827,6 +8827,13 @@ "aria-label-default": "Wählen Sie eine Farbe", "aria-label-selected-color": "{{colorLabel}} Farbe" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "Löschen", "cancel": "Abbrechen", @@ -9024,6 +9031,8 @@ "interactive-table": { "aria-label-collapse-all": "Alle Zeilen einklappen", "aria-label-expand-all": "Alle Zeilen erweitern", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "Zeile umschalten erweitert", "tooltip-collapse-all": "Alle Zeilen einklappen", "tooltip-expand-all": "Alle Zeilen erweitern" @@ -12425,13 +12434,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index a02d2517fa8..1043c4637de 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -8827,6 +8827,13 @@ "aria-label-default": "Elegir un color", "aria-label-selected-color": "{{colorLabel}} color" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "Eliminar", "cancel": "Cancelar", @@ -9024,6 +9031,8 @@ "interactive-table": { "aria-label-collapse-all": "Contraer todas las filas", "aria-label-expand-all": "Expandir todas las filas", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "Alternar fila expandida", "tooltip-collapse-all": "Contraer todas las filas", "tooltip-expand-all": "Expandir todas las filas" @@ -12425,13 +12434,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 25d9783c652..d9aa7756cba 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -8827,6 +8827,13 @@ "aria-label-default": "Choisir une couleur", "aria-label-selected-color": "Couleur {{colorLabel}}" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "Supprimer", "cancel": "Annuler", @@ -9024,6 +9031,8 @@ "interactive-table": { "aria-label-collapse-all": "Réduire toutes les lignes", "aria-label-expand-all": "Développer toutes les lignes", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "Basculer la ligne développée", "tooltip-collapse-all": "Réduire toutes les lignes", "tooltip-expand-all": "Développer toutes les lignes" @@ -12425,13 +12434,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 31d51ce3843..a7a7b5f54e6 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -8827,6 +8827,13 @@ "aria-label-default": "Válasszon színt", "aria-label-selected-color": "{{colorLabel}} szín" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "Törlés", "cancel": "Mégse", @@ -9024,6 +9031,8 @@ "interactive-table": { "aria-label-collapse-all": "Minden sor összecsukása", "aria-label-expand-all": "Minden sor kibontása", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "Sorkibontás váltása", "tooltip-collapse-all": "Minden sor összecsukása", "tooltip-expand-all": "Minden sor kibontása" @@ -12425,13 +12434,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 3bab7931da8..2524fef02ef 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -8792,6 +8792,13 @@ "aria-label-default": "Pilih warna", "aria-label-selected-color": "warna {{colorLabel}}" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "Hapus", "cancel": "Batalkan", @@ -8989,6 +8996,8 @@ "interactive-table": { "aria-label-collapse-all": "Ciutkan semua baris", "aria-label-expand-all": "Perluas semua baris", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "Alihkan baris diperluas", "tooltip-collapse-all": "Ciutkan semua baris", "tooltip-expand-all": "Perluas semua baris" @@ -12373,13 +12382,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 1bbbd8daee3..4ca3f0fe469 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -8827,6 +8827,13 @@ "aria-label-default": "Scegli un colore", "aria-label-selected-color": "Colore {{colorLabel}}" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "Elimina", "cancel": "Annulla", @@ -9024,6 +9031,8 @@ "interactive-table": { "aria-label-collapse-all": "Ridurre tutte le righe", "aria-label-expand-all": "Espandi tutte le righe", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "Attiva/disattiva riga espansa", "tooltip-collapse-all": "Ridurre tutte le righe", "tooltip-expand-all": "Espandi tutte le righe" @@ -12425,13 +12434,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 5031d88730e..7c22a029f1c 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -8792,6 +8792,13 @@ "aria-label-default": "色を選択", "aria-label-selected-color": "{{colorLabel}}色" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "削除", "cancel": "キャンセル", @@ -8989,6 +8996,8 @@ "interactive-table": { "aria-label-collapse-all": "すべての行を折りたたむ", "aria-label-expand-all": "すべての行を広げる", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "行の展開を切り替える", "tooltip-collapse-all": "すべての行を折りたたむ", "tooltip-expand-all": "すべての行を広げる" @@ -12373,13 +12382,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index f3505d1f337..bbfa4375120 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -8792,6 +8792,13 @@ "aria-label-default": "색상 선택", "aria-label-selected-color": "{{colorLabel}} 색상" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "삭제", "cancel": "취소", @@ -8989,6 +8996,8 @@ "interactive-table": { "aria-label-collapse-all": "모든 행 접기", "aria-label-expand-all": "모든 행 펼치기", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "토글하여 펼쳐진 행으로 전환", "tooltip-collapse-all": "모든 행 접기", "tooltip-expand-all": "모든 행 펼치기" @@ -12373,13 +12382,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index ad8203942df..d02c0beb6fd 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -8827,6 +8827,13 @@ "aria-label-default": "Kies een kleur", "aria-label-selected-color": "Kleur {{colorLabel}}" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "Verwijderen", "cancel": "Annuleren", @@ -9024,6 +9031,8 @@ "interactive-table": { "aria-label-collapse-all": "Alle rijen samenvouwen", "aria-label-expand-all": "Alle rijen uitvouwen", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "Rij in-/uitschakelen uitgevouwen", "tooltip-collapse-all": "Alle rijen samenvouwen", "tooltip-expand-all": "Alle rijen uitvouwen" @@ -12425,13 +12434,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index f34809b9cbe..51509926ba4 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -8897,6 +8897,13 @@ "aria-label-default": "Wybierz kolor", "aria-label-selected-color": "Kolor {{colorLabel}}" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "Usuń", "cancel": "Anuluj", @@ -9094,6 +9101,8 @@ "interactive-table": { "aria-label-collapse-all": "Zwiń wszystkie wiersze", "aria-label-expand-all": "Rozwiń wszystkie wiersze", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "Przełącz rozwinięcie wiersza", "tooltip-collapse-all": "Zwiń wszystkie wiersze", "tooltip-expand-all": "Rozwiń wszystkie wiersze" @@ -12529,13 +12538,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index d8822bc8635..dbc6665356a 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -8827,6 +8827,13 @@ "aria-label-default": "Escolha uma cor", "aria-label-selected-color": "cor {{colorLabel}}" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "Excluir", "cancel": "Cancelar", @@ -9024,6 +9031,8 @@ "interactive-table": { "aria-label-collapse-all": "Recolher todas as linhas", "aria-label-expand-all": "Expandir todas as linhas", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "Alternar linha expandida", "tooltip-collapse-all": "Recolher todas as linhas", "tooltip-expand-all": "Expandir todas as linhas" @@ -12425,13 +12434,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index 1fed27f2a62..b09de6f172a 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -8827,6 +8827,13 @@ "aria-label-default": "Escolher uma cor", "aria-label-selected-color": "cor {{colorLabel}} " }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "Eliminar", "cancel": "Cancelar", @@ -9024,6 +9031,8 @@ "interactive-table": { "aria-label-collapse-all": "Recolher todas as linhas", "aria-label-expand-all": "Expandir todas as linhas", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "Alternar linha expandida", "tooltip-collapse-all": "Recolher todas as linhas", "tooltip-expand-all": "Expandir todas as linhas" @@ -12425,13 +12434,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index 43edc80f5dc..8cb3e2dcd7c 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -8897,6 +8897,13 @@ "aria-label-default": "Выбрать цвет", "aria-label-selected-color": "{{colorLabel}} цвет" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "Удалить", "cancel": "Отмена", @@ -9094,6 +9101,8 @@ "interactive-table": { "aria-label-collapse-all": "Свернуть все строки", "aria-label-expand-all": "Развернуть все строки", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "Развернута строка переключения", "tooltip-collapse-all": "Свернуть все строки", "tooltip-expand-all": "Развернуть все строки" @@ -12529,13 +12538,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index d983d726e59..3e56a5f66c7 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -8827,6 +8827,13 @@ "aria-label-default": "Välj en färg", "aria-label-selected-color": "{{colorLabel}} färg" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "Radera", "cancel": "Avbryt", @@ -9024,6 +9031,8 @@ "interactive-table": { "aria-label-collapse-all": "Dölj alla rader", "aria-label-expand-all": "Expandera alla rader", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "Växla rad utökad", "tooltip-collapse-all": "Dölj alla rader", "tooltip-expand-all": "Expandera alla rader" @@ -12425,13 +12434,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 30d0c3601ae..13d03187dea 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -8827,6 +8827,13 @@ "aria-label-default": "Bir renk seçin", "aria-label-selected-color": "{{colorLabel}} rengi" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "Sil", "cancel": "İptal", @@ -9024,6 +9031,8 @@ "interactive-table": { "aria-label-collapse-all": "Tüm satırları daralt", "aria-label-expand-all": "Tüm satırları genişlet", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "Satır genişletmeyi aç/kapat", "tooltip-collapse-all": "Tüm satırları daralt", "tooltip-expand-all": "Tüm satırları genişlet" @@ -12425,13 +12434,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 33bb2fc3d08..41582b2fe9c 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -8792,6 +8792,13 @@ "aria-label-default": "选择颜色", "aria-label-selected-color": "{{colorLabel}} 颜色" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "删除", "cancel": "取消", @@ -8989,6 +8996,8 @@ "interactive-table": { "aria-label-collapse-all": "折叠所有行", "aria-label-expand-all": "展开所有行", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "切换行展开", "tooltip-collapse-all": "折叠所有行", "tooltip-expand-all": "展开所有行" @@ -12373,13 +12382,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index 6fbcd57b192..36f3ef7a410 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -8792,6 +8792,13 @@ "aria-label-default": "挑選顏色", "aria-label-selected-color": "{{colorLabel}} 顏色" }, + "components": { + "sparkline": { + "warning": { + "too-few-values": "" + } + } + }, "confirm-button": { "aria-label-delete": "刪除", "cancel": "取消", @@ -8989,6 +8996,8 @@ "interactive-table": { "aria-label-collapse-all": "收闔所有列", "aria-label-expand-all": "展開所有列", + "aria-label-sort-column": "", + "expand-row-header": "", "expand-row-tooltip": "切換列展開", "tooltip-collapse-all": "收闔所有列", "tooltip-expand-all": "展開所有列" @@ -12373,13 +12382,12 @@ "effects": { "bar-glow": "", "center-glow": "", + "gradient": "", + "label": "", "rounded-bars": "", "spotlight": "", "spotlight-tooltip": "" }, - "gradient": "", - "gradient-auto": "", - "gradient-none": "", "segment-count": "", "segment-spacing": "", "shape": "", From fe4c615b3d44a2979b52ca5f34cc249889c6ffd2 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:34:21 +0100 Subject: [PATCH 028/297] Scopes: Sync nested scopes navigation open folders to URL (#114786) * Sync nav_scope_path with url * Let the current active scope remain if it is a child of the selected subscope * Remove location updates based on nav_scope_path to maintain expanded folders * Fix folder tests * Remove console logs * Better mock for changeScopes * Update test to support the new calls * Update test with function inputs * Fix failinging test * Add tests and add isEqual check for fetching new subscopes --- .../app/features/scopes/ScopesService.test.ts | 303 +++++++++++++++++- public/app/features/scopes/ScopesService.ts | 57 +++- .../scopes/dashboards/ScopesDashboards.tsx | 7 +- .../ScopesDashboardsService.test.ts | 48 +++ .../dashboards/ScopesDashboardsService.ts | 64 +++- .../dashboards/ScopesDashboardsTree.tsx | 11 +- .../ScopesDashboardsTreeFolderItem.test.tsx | 7 +- .../ScopesDashboardsTreeFolderItem.tsx | 24 +- .../ScopesNavigationTreeLink.test.tsx | 2 +- .../dashboards/ScopesNavigationTreeLink.tsx | 16 +- .../dashboards/scopeNavgiationUtils.test.ts | 199 +++++++++++- .../scopes/dashboards/scopeNavgiationUtils.ts | 53 +++ .../selector/ScopesSelectorService.test.ts | 2 +- .../scopes/selector/ScopesSelectorService.ts | 2 +- 14 files changed, 757 insertions(+), 38 deletions(-) diff --git a/public/app/features/scopes/ScopesService.test.ts b/public/app/features/scopes/ScopesService.test.ts index 51c50cc9ff9..fdc0c8bd598 100644 --- a/public/app/features/scopes/ScopesService.test.ts +++ b/public/app/features/scopes/ScopesService.test.ts @@ -22,8 +22,8 @@ describe('ScopesService', () => { | undefined; let dashboardsStateSubscription: | (( - state: { navigationScope?: string; drawerOpened: boolean }, - prevState: { navigationScope?: string; drawerOpened: boolean } + state: { navigationScope?: string; drawerOpened: boolean; navScopePath?: string[] }, + prevState: { navigationScope?: string; drawerOpened: boolean; navScopePath?: string[] } ) => void) | undefined; @@ -56,7 +56,7 @@ describe('ScopesService', () => { selectorStateSubscription = callback; return { unsubscribe: jest.fn() }; }), - changeScopes: jest.fn(), + changeScopes: jest.fn().mockResolvedValue(undefined), resolvePathToRoot: jest.fn().mockResolvedValue({ path: [], tree: {} }), } as unknown as jest.Mocked; @@ -71,6 +71,7 @@ describe('ScopesService', () => { loading: false, searchQuery: '', navigationScope: undefined, + navScopePath: undefined, }, stateObservable: new BehaviorSubject({ drawerOpened: false, @@ -82,12 +83,14 @@ describe('ScopesService', () => { loading: false, searchQuery: '', navigationScope: undefined, + navScopePath: undefined, }), subscribeToState: jest.fn((callback) => { dashboardsStateSubscription = callback; return { unsubscribe: jest.fn() }; }), setNavigationScope: jest.fn(), + setNavScopePath: jest.fn(), } as unknown as jest.Mocked; locationService = { @@ -188,7 +191,7 @@ describe('ScopesService', () => { service = new ScopesService(selectorService, dashboardsService, locationService); - expect(dashboardsService.setNavigationScope).toHaveBeenCalledWith('navScope1'); + expect(dashboardsService.setNavigationScope).toHaveBeenCalledWith('navScope1', undefined, undefined); }); it('should read navigation_scope along with other scope parameters', () => { @@ -199,7 +202,7 @@ describe('ScopesService', () => { service = new ScopesService(selectorService, dashboardsService, locationService); - expect(dashboardsService.setNavigationScope).toHaveBeenCalledWith('navScope1'); + expect(dashboardsService.setNavigationScope).toHaveBeenCalledWith('navScope1', undefined, undefined); expect(selectorService.changeScopes).toHaveBeenCalledWith(['scope1'], undefined, 'node1', false); }); @@ -213,6 +216,45 @@ describe('ScopesService', () => { expect(dashboardsService.setNavigationScope).not.toHaveBeenCalled(); }); + + it('should read nav_scope_path along with navigation_scope from URL on init', () => { + locationService.getLocation = jest.fn().mockReturnValue({ + pathname: '/test', + search: '?navigation_scope=navScope1&nav_scope_path=mimir%2Cloki', + }); + + service = new ScopesService(selectorService, dashboardsService, locationService); + + expect(dashboardsService.setNavigationScope).toHaveBeenCalledWith('navScope1', undefined, ['mimir', 'loki']); + }); + + it('should handle nav_scope_path without navigation_scope by calling setNavScopePath after changeScopes', async () => { + locationService.getLocation = jest.fn().mockReturnValue({ + pathname: '/test', + search: '?scopes=scope1&nav_scope_path=mimir', + }); + + service = new ScopesService(selectorService, dashboardsService, locationService); + + // Wait for the changeScopes promise to resolve + await Promise.resolve(); + + expect(dashboardsService.setNavScopePath).toHaveBeenCalledWith(['mimir']); + }); + + it('should handle URL-encoded nav_scope_path values', () => { + locationService.getLocation = jest.fn().mockReturnValue({ + pathname: '/test', + search: '?navigation_scope=navScope1&nav_scope_path=' + encodeURIComponent('folder one,folder two'), + }); + + service = new ScopesService(selectorService, dashboardsService, locationService); + + expect(dashboardsService.setNavigationScope).toHaveBeenCalledWith('navScope1', undefined, [ + 'folder one', + 'folder two', + ]); + }); }); describe('URL synchronization', () => { @@ -346,16 +388,22 @@ describe('ScopesService', () => { { navigationScope: 'navScope1', drawerOpened: true, + navScopePath: undefined, }, { navigationScope: undefined, drawerOpened: false, + navScopePath: undefined, } ); - expect(locationService.partial).toHaveBeenCalledWith({ - navigation_scope: 'navScope1', - }); + expect(locationService.partial).toHaveBeenCalledWith( + { + navigation_scope: 'navScope1', + nav_scope_path: null, + }, + true + ); }); it('should update navigation_scope in URL when navigationScope changes', () => { @@ -367,16 +415,22 @@ describe('ScopesService', () => { { navigationScope: 'navScope2', drawerOpened: true, + navScopePath: undefined, }, { navigationScope: 'navScope1', drawerOpened: true, + navScopePath: undefined, } ); - expect(locationService.partial).toHaveBeenCalledWith({ - navigation_scope: 'navScope2', - }); + expect(locationService.partial).toHaveBeenCalledWith( + { + navigation_scope: 'navScope2', + nav_scope_path: null, + }, + true + ); }); it('should not update URL when navigationScope has not changed', () => { @@ -390,10 +444,12 @@ describe('ScopesService', () => { { navigationScope: 'navScope1', drawerOpened: true, + navScopePath: undefined, }, { navigationScope: 'navScope1', drawerOpened: false, + navScopePath: undefined, } ); @@ -409,16 +465,126 @@ describe('ScopesService', () => { { navigationScope: undefined, drawerOpened: false, + navScopePath: undefined, }, { navigationScope: 'navScope1', drawerOpened: true, + navScopePath: undefined, } ); - expect(locationService.partial).toHaveBeenCalledWith({ - navigation_scope: undefined, - }); + expect(locationService.partial).toHaveBeenCalledWith( + { + navigation_scope: null, + nav_scope_path: null, + }, + true + ); + }); + + it('should write nav_scope_path to URL when navScopePath changes', () => { + if (!dashboardsStateSubscription) { + throw new Error('dashboardsStateSubscription not set'); + } + + dashboardsStateSubscription( + { + navigationScope: 'navScope1', + drawerOpened: true, + navScopePath: ['mimir', 'loki'], + }, + { + navigationScope: 'navScope1', + drawerOpened: true, + navScopePath: undefined, + } + ); + + expect(locationService.partial).toHaveBeenCalledWith( + { + navigation_scope: 'navScope1', + nav_scope_path: encodeURIComponent('mimir,loki'), + }, + true + ); + }); + + it('should update nav_scope_path in URL when navScopePath changes', () => { + if (!dashboardsStateSubscription) { + throw new Error('dashboardsStateSubscription not set'); + } + + dashboardsStateSubscription( + { + navigationScope: 'navScope1', + drawerOpened: true, + navScopePath: ['mimir', 'loki', 'tempo'], + }, + { + navigationScope: 'navScope1', + drawerOpened: true, + navScopePath: ['mimir', 'loki'], + } + ); + + expect(locationService.partial).toHaveBeenCalledWith( + { + navigation_scope: 'navScope1', + nav_scope_path: encodeURIComponent('mimir,loki,tempo'), + }, + true + ); + }); + + it('should clear nav_scope_path from URL when navScopePath becomes empty', () => { + if (!dashboardsStateSubscription) { + throw new Error('dashboardsStateSubscription not set'); + } + + dashboardsStateSubscription( + { + navigationScope: 'navScope1', + drawerOpened: true, + navScopePath: [], + }, + { + navigationScope: 'navScope1', + drawerOpened: true, + navScopePath: ['mimir'], + } + ); + + expect(locationService.partial).toHaveBeenCalledWith( + { + navigation_scope: 'navScope1', + nav_scope_path: null, + }, + true + ); + }); + + it('should not update URL when only drawerOpened changes but navigationScope and navScopePath remain the same', () => { + if (!dashboardsStateSubscription) { + throw new Error('dashboardsStateSubscription not set'); + } + + jest.clearAllMocks(); + + dashboardsStateSubscription( + { + navigationScope: 'navScope1', + drawerOpened: false, + navScopePath: ['mimir'], + }, + { + navigationScope: 'navScope1', + drawerOpened: true, + navScopePath: ['mimir'], + } + ); + + expect(locationService.partial).not.toHaveBeenCalled(); }); }); @@ -457,4 +623,113 @@ describe('ScopesService', () => { ); }); }); + + describe('back/forward navigation handling', () => { + let locationSubject: BehaviorSubject<{ pathname: string; search: string }>; + + beforeEach(() => { + locationSubject = new BehaviorSubject({ + pathname: '/test', + search: '', + }); + + locationService.getLocation = jest.fn().mockReturnValue({ + pathname: '/test', + search: '', + }); + locationService.getLocationObservable = jest.fn().mockReturnValue(locationSubject); + + // Set initial state for dashboards service + dashboardsService.state.navigationScope = undefined; + dashboardsService.state.navScopePath = undefined; + + service = new ScopesService(selectorService, dashboardsService, locationService); + service.setEnabled(true); + + jest.clearAllMocks(); + }); + + it('should update navigation scope when URL changes via back/forward', () => { + // Simulate URL change (e.g., browser back button) + locationSubject.next({ + pathname: '/test', + search: '?navigation_scope=navScope1', + }); + + expect(dashboardsService.setNavigationScope).toHaveBeenCalledWith('navScope1', undefined, undefined); + }); + + it('should update nav_scope_path when URL changes via back/forward', () => { + // Set current state + dashboardsService.state.navigationScope = 'navScope1'; + dashboardsService.state.navScopePath = undefined; + + // Simulate URL change with nav_scope_path + locationSubject.next({ + pathname: '/test', + search: '?navigation_scope=navScope1&nav_scope_path=' + encodeURIComponent('mimir,loki'), + }); + + expect(dashboardsService.setNavScopePath).toHaveBeenCalledWith(['mimir', 'loki']); + }); + + it('should clear navigation scope when removed from URL via back/forward', () => { + // Set current state + dashboardsService.state.navigationScope = 'navScope1'; + dashboardsService.state.navScopePath = ['mimir']; + + // Simulate URL change (navigation scope removed) + locationSubject.next({ + pathname: '/test', + search: '', + }); + + expect(dashboardsService.setNavigationScope).toHaveBeenCalledWith(undefined); + }); + + it('should handle navigation scope change along with nav_scope_path', () => { + // Set current state + dashboardsService.state.navigationScope = 'navScope1'; + dashboardsService.state.navScopePath = ['mimir']; + + // Simulate URL change to different navigation scope with new path + locationSubject.next({ + pathname: '/test', + search: '?navigation_scope=navScope2&nav_scope_path=' + encodeURIComponent('loki,tempo'), + }); + + expect(dashboardsService.setNavigationScope).toHaveBeenCalledWith('navScope2', undefined, ['loki', 'tempo']); + }); + + it('should handle URL-encoded navigation_scope from back/forward', () => { + // Set current state + dashboardsService.state.navigationScope = undefined; + + // Simulate URL change with encoded navigation scope + locationSubject.next({ + pathname: '/test', + search: '?navigation_scope=' + encodeURIComponent('scope with spaces'), + }); + + expect(dashboardsService.setNavigationScope).toHaveBeenCalledWith('scope with spaces', undefined, undefined); + }); + + it('should handle nav_scope_path change without navigation_scope', async () => { + // Set current state - no navigation scope but has nav scope path + dashboardsService.state.navigationScope = undefined; + dashboardsService.state.navScopePath = undefined; + selectorService.state.appliedScopes = [{ scopeId: 'scope1' }]; + + // Simulate URL change with only nav_scope_path + locationSubject.next({ + pathname: '/test', + search: '?scopes=scope1&nav_scope_path=mimir', + }); + + // Wait for changeScopes promise + await Promise.resolve(); + + expect(dashboardsService.setNavScopePath).toHaveBeenCalledWith(['mimir']); + }); + }); }); diff --git a/public/app/features/scopes/ScopesService.ts b/public/app/features/scopes/ScopesService.ts index 1d0be39f56c..c92d6328749 100644 --- a/public/app/features/scopes/ScopesService.ts +++ b/public/app/features/scopes/ScopesService.ts @@ -5,6 +5,7 @@ import { map, distinctUntilChanged } from 'rxjs/operators'; import { LocationService, ScopesContextValue, ScopesContextValueState } from '@grafana/runtime'; import { ScopesDashboardsService } from './dashboards/ScopesDashboardsService'; +import { deserializeFolderPath, serializeFolderPath } from './dashboards/scopeNavgiationUtils'; import { ScopesSelectorService } from './selector/ScopesSelectorService'; export interface State { @@ -72,12 +73,21 @@ export class ScopesService implements ScopesContextValue { const queryParams = new URLSearchParams(locationService.getLocation().search); const scopeNodeId = queryParams.get('scope_node'); const navigationScope = queryParams.get('navigation_scope'); + const navScopePath = queryParams.get('nav_scope_path'); if (navigationScope) { - this.dashboardsService.setNavigationScope(navigationScope); + this.dashboardsService.setNavigationScope( + navigationScope, + undefined, + navScopePath ? deserializeFolderPath(navScopePath) : undefined + ); } - this.changeScopes(queryParams.getAll('scopes'), undefined, scopeNodeId ?? undefined); + this.changeScopes(queryParams.getAll('scopes'), undefined, scopeNodeId ?? undefined).then(() => { + if (navScopePath && !navigationScope) { + this.dashboardsService.setNavScopePath(deserializeFolderPath(navScopePath)); + } + }); // Pre-load scope node (which loads parent too) const nodeToPreload = scopeNodeId; @@ -99,6 +109,9 @@ export class ScopesService implements ScopesContextValue { const scopes = queryParams.getAll('scopes'); const scopeNodeId = queryParams.get('scope_node'); + const navigationScope = queryParams.get('navigation_scope'); + const navScopePath = queryParams.get('nav_scope_path'); + // Check if new scopes are different from the old scopes const currentScopes = this.selectorService.state.appliedScopes.map((scope) => scope.scopeId); if (scopes.length && !isEqual(scopes, currentScopes)) { @@ -107,6 +120,31 @@ export class ScopesService implements ScopesContextValue { // changes the URL directly, it would trigger a reload so scopes would still be reset. this.changeScopes(scopes, undefined, scopeNodeId ?? undefined); } + + // Handle navigation_scope and nav_scope_path changes from back/forward navigation + const currentNavigationScope = this.dashboardsService.state.navigationScope; + const currentNavScopePath = this.dashboardsService.state.navScopePath; + const newNavScopePath = navScopePath ? deserializeFolderPath(navScopePath) : undefined; + const decodedNavigationScope = navigationScope ? decodeURIComponent(navigationScope) : undefined; + + const navigationScopeChanged = decodedNavigationScope !== currentNavigationScope; + const navScopePathChanged = !isEqual(newNavScopePath, currentNavScopePath); + + if (navigationScopeChanged) { + // Navigation scope changed - do full update + if (decodedNavigationScope) { + this.dashboardsService.setNavigationScope(decodedNavigationScope, undefined, newNavScopePath); + } else if (newNavScopePath?.length) { + this.changeScopes(scopes, undefined, scopeNodeId ?? undefined).then(() => { + this.dashboardsService.setNavScopePath(newNavScopePath); + }); + } else { + this.dashboardsService.setNavigationScope(undefined); + } + } else if (navScopePathChanged) { + // Navigation scope unchanged but path changed + this.dashboardsService.setNavScopePath(newNavScopePath); + } }) ); @@ -137,10 +175,17 @@ export class ScopesService implements ScopesContextValue { // Update the URL based on change in the navigation scope this.subscriptions.push( this.dashboardsService.subscribeToState((state, prevState) => { - if (state.navigationScope !== prevState.navigationScope) { - this.locationService.partial({ - navigation_scope: state.navigationScope, - }); + if ( + state.navigationScope !== prevState.navigationScope || + !isEqual(state.navScopePath, prevState.navScopePath) + ) { + this.locationService.partial( + { + navigation_scope: state.navigationScope ? encodeURIComponent(state.navigationScope) : null, + nav_scope_path: state.navScopePath?.length ? serializeFolderPath(state.navScopePath) : null, + }, + true + ); } }) ); diff --git a/public/app/features/scopes/dashboards/ScopesDashboards.tsx b/public/app/features/scopes/dashboards/ScopesDashboards.tsx index 3db9344e6c1..7a2fc7832f3 100644 --- a/public/app/features/scopes/dashboards/ScopesDashboards.tsx +++ b/public/app/features/scopes/dashboards/ScopesDashboards.tsx @@ -67,7 +67,12 @@ export function ScopesDashboards() { /> ) : filteredFolders[''] ? ( - + ) : (

diff --git a/public/app/features/scopes/dashboards/ScopesDashboardsService.test.ts b/public/app/features/scopes/dashboards/ScopesDashboardsService.test.ts index 52368b4577d..ca621106f81 100644 --- a/public/app/features/scopes/dashboards/ScopesDashboardsService.test.ts +++ b/public/app/features/scopes/dashboards/ScopesDashboardsService.test.ts @@ -839,4 +839,52 @@ describe('ScopesDashboardsService', () => { expect(service.state.drawerOpened).toBe(true); }); }); + + describe('setNavScopePath', () => { + beforeEach(() => { + (locationService.getLocation as jest.Mock).mockReturnValue({ pathname: '/' } as Location); + }); + + it('should set nav scope path', async () => { + await service.setNavScopePath(['mimir']); + expect(service.state.navScopePath).toEqual(['mimir']); + }); + + it('should replace existing path with new path', async () => { + await service.setNavScopePath(['mimir']); + expect(service.state.navScopePath).toEqual(['mimir']); + + await service.setNavScopePath(['loki']); + expect(service.state.navScopePath).toEqual(['loki']); + }); + + it('should handle multiple scopes in path', async () => { + await service.setNavScopePath(['mimir', 'loki']); + expect(service.state.navScopePath).toEqual(['mimir', 'loki']); + }); + + it('should clear path with empty array', async () => { + await service.setNavScopePath(['mimir', 'loki']); + expect(service.state.navScopePath).toEqual(['mimir', 'loki']); + + await service.setNavScopePath([]); + expect(service.state.navScopePath).toEqual([]); + }); + + it('should handle undefined path as empty array', async () => { + await service.setNavScopePath(['mimir']); + expect(service.state.navScopePath).toEqual(['mimir']); + + await service.setNavScopePath(undefined); + expect(service.state.navScopePath).toEqual([]); + }); + + it('should not update state if path is unchanged', async () => { + await service.setNavScopePath(['mimir']); + + await service.setNavScopePath(['mimir']); + // Path should remain the same + expect(service.state.navScopePath).toEqual(['mimir']); + }); + }); }); diff --git a/public/app/features/scopes/dashboards/ScopesDashboardsService.ts b/public/app/features/scopes/dashboards/ScopesDashboardsService.ts index 55ea5ab737a..96518cba5f7 100644 --- a/public/app/features/scopes/dashboards/ScopesDashboardsService.ts +++ b/public/app/features/scopes/dashboards/ScopesDashboardsService.ts @@ -7,8 +7,13 @@ import { config, locationService } from '@grafana/runtime'; import { ScopesApiClient } from '../ScopesApiClient'; import { ScopesServiceBase } from '../ScopesServiceBase'; -import { isCurrentPath } from './scopeNavgiationUtils'; -import { ScopeNavigation, SuggestedNavigationsFoldersMap, SuggestedNavigationsMap } from './types'; +import { buildSubScopePath, isCurrentPath } from './scopeNavgiationUtils'; +import { + ScopeNavigation, + SuggestedNavigationsFolder, + SuggestedNavigationsFoldersMap, + SuggestedNavigationsMap, +} from './types'; interface ScopesDashboardsServiceState { // State of the drawer showing related dashboards @@ -24,6 +29,8 @@ interface ScopesDashboardsServiceState { loading: boolean; searchQuery: string; navigationScope?: string; + // Path of subScopes which should be expanded + navScopePath?: string[]; } export class ScopesDashboardsService extends ScopesServiceBase { @@ -38,6 +45,7 @@ export class ScopesDashboardsService extends ScopesServiceBase { + const subScope = subScopePath[subScopePath.length - 1]; + const path = buildSubScopePath(subScope, this.state.folders); + + // Get path to the folder - path can now be undefined + if (path && path.length > 0) { + this.updateFolder(path, true); + } + }; + + public setNavScopePath = async (navScopePath?: string[]) => { + const navScopePathArray = navScopePath ?? []; + + if (!isEqual(navScopePathArray, this.state.navScopePath)) { + this.updateState({ navScopePath: navScopePathArray }); + + for (const subScope of navScopePathArray) { + // Find the actual path to the folder with this subScopeName + const folderPath = buildSubScopePath(subScope, this.state.folders); + if (folderPath && folderPath.length > 0) { + await this.fetchSubScopeItems(folderPath, subScope); + this.openSubScopeFolder([subScope]); + } + } + } + }; + // The fallbackScopeNames is used to fetch the ScopeNavigations for the current dashboard when the navigationScope is not set. // You only need to awaut this function if you need to wait for the dashboards to be fetched before doing something else. - public setNavigationScope = async (navigationScope?: string, fallbackScopeNames?: string[]) => { + public setNavigationScope = async ( + navigationScope?: string, + fallbackScopeNames?: string[], + navScopePath?: string[] + ) => { if (this.state.navigationScope === navigationScope) { return; } @@ -67,6 +106,7 @@ export class ScopesDashboardsService extends ScopesServiceBase 0 }); await this.fetchDashboards(forScopeNames); + await this.setNavScopePath(navScopePath); }; // Expand the group that matches the current path, if it is not already expanded @@ -148,6 +188,15 @@ export class ScopesDashboardsService extends ScopesServiceBase { + // Check if folder already has content - skip fetching to preserve existing state + const targetFolder = this.getFolder(path); + if ( + targetFolder && + (Object.keys(targetFolder.folders).length > 0 || Object.keys(targetFolder.suggestedNavigations).length > 0) + ) { + return; + } + let subScopeFolders: SuggestedNavigationsFoldersMap | undefined; try { @@ -208,6 +257,15 @@ export class ScopesDashboardsService extends ScopesServiceBase { + let folder: SuggestedNavigationsFoldersMap = this.state.folders; + for (let i = 0; i < path.length - 1; i++) { + folder = folder[path[i]]?.folders ?? {}; + } + return folder[path[path.length - 1]]; + }; + public changeSearchQuery = (searchQuery: string) => { searchQuery = searchQuery ?? ''; diff --git a/public/app/features/scopes/dashboards/ScopesDashboardsTree.tsx b/public/app/features/scopes/dashboards/ScopesDashboardsTree.tsx index 7e7601f3b06..cbaea4c3e84 100644 --- a/public/app/features/scopes/dashboards/ScopesDashboardsTree.tsx +++ b/public/app/features/scopes/dashboards/ScopesDashboardsTree.tsx @@ -12,10 +12,17 @@ export interface ScopesDashboardsTreeProps { subScope?: string; folders: SuggestedNavigationsFoldersMap; folderPath: string[]; + subScopePath?: string[]; onFolderUpdate: OnFolderUpdate; } -export function ScopesDashboardsTree({ subScope, folders, folderPath, onFolderUpdate }: ScopesDashboardsTreeProps) { +export function ScopesDashboardsTree({ + subScopePath, + subScope, + folders, + folderPath, + onFolderUpdate, +}: ScopesDashboardsTreeProps) { const [queryParams] = useQueryParams(); const styles = useStyles2(getStyles); @@ -54,6 +61,7 @@ export function ScopesDashboardsTree({ subScope, folders, folderPath, onFolderUp {regularNavigations.map((navigation) => ( ( ({ @@ -133,7 +136,7 @@ describe('ScopesDashboardsTreeFolderItem', () => { const exchangeButton = screen.getByRole('button', { name: /change root scope/i }); await user.click(exchangeButton); - expect(mockScopesDashboardsService.setNavigationScope).toHaveBeenCalledWith(undefined, ['subScope1']); + expect(mockScopesDashboardsService.setNavigationScope).toHaveBeenCalledWith(undefined, undefined, []); }); it('calls changeScopes when exchange icon is clicked', async () => { @@ -152,7 +155,7 @@ describe('ScopesDashboardsTreeFolderItem', () => { const exchangeButton = screen.getByRole('button', { name: /change root scope/i }); await user.click(exchangeButton); - expect(mockScopesSelectorService.changeScopes).toHaveBeenCalledWith(['subScope1']); + expect(mockScopesSelectorService.changeScopes).toHaveBeenCalledWith(['subScope1'], undefined, undefined, false); }); it('passes subScope prop to ScopesDashboardsTree when folder is expanded', () => { diff --git a/public/app/features/scopes/dashboards/ScopesDashboardsTreeFolderItem.tsx b/public/app/features/scopes/dashboards/ScopesDashboardsTreeFolderItem.tsx index 9d572a4d54c..d6846f432ac 100644 --- a/public/app/features/scopes/dashboards/ScopesDashboardsTreeFolderItem.tsx +++ b/public/app/features/scopes/dashboards/ScopesDashboardsTreeFolderItem.tsx @@ -14,9 +14,11 @@ export interface ScopesDashboardsTreeFolderItemProps { folderPath: string[]; folders: SuggestedNavigationsFoldersMap; onFolderUpdate: OnFolderUpdate; + subScopePath?: string[]; } export function ScopesDashboardsTreeFolderItem({ + subScopePath, folder, folderPath, folders, @@ -53,12 +55,27 @@ export function ScopesDashboardsTreeFolderItem({ scope: folder.subScopeName || '', })} name="exchange-alt" - onClick={(e) => { + onClick={async (e) => { e.preventDefault(); e.stopPropagation(); if (folder.subScopeName && scopesSelectorService) { - scopesDashboardsService?.setNavigationScope(undefined, [folder.subScopeName]); - scopesSelectorService.changeScopes([folder.subScopeName]); + const activeSubScopePath = scopesDashboardsService?.state.navScopePath; + // Check if the active scope is a child of the current folder's scope + const activeScope = activeSubScopePath?.[activeSubScopePath.length - 1]; + const folderLocationInActivePath = activeSubScopePath?.indexOf(folder.subScopeName) ?? -1; + + await scopesDashboardsService?.setNavigationScope( + folderLocationInActivePath >= 0 ? folder.subScopeName : undefined, + undefined, + activeSubScopePath?.slice(folderLocationInActivePath + 1) ?? [] + ); + // Now changeScopes will skip fetchDashboards because navigationScope is set + scopesSelectorService.changeScopes( + folderLocationInActivePath >= 0 && activeScope ? [activeScope] : [folder.subScopeName], + undefined, + undefined, + false + ); } }} /> @@ -68,6 +85,7 @@ export function ScopesDashboardsTreeFolderItem({ {folder.expanded && (

{ const link = screen.getByTestId('scopes-dashboards-test-id'); await userEvent.click(link); - expect(mockScopesDashboardsService.setNavigationScope).toHaveBeenCalledWith('currentScope'); + expect(mockScopesDashboardsService.setNavigationScope).toHaveBeenCalledWith('currentScope', undefined, undefined); }); it('should not set navigation scope when already set', async () => { diff --git a/public/app/features/scopes/dashboards/ScopesNavigationTreeLink.tsx b/public/app/features/scopes/dashboards/ScopesNavigationTreeLink.tsx index b7722edcf8c..67b1649ee3c 100644 --- a/public/app/features/scopes/dashboards/ScopesNavigationTreeLink.tsx +++ b/public/app/features/scopes/dashboards/ScopesNavigationTreeLink.tsx @@ -8,16 +8,17 @@ import { Icon, useStyles2 } from '@grafana/ui'; import { useScopesServices } from '../ScopesContextProvider'; -import { isCurrentPath, normalizePath } from './scopeNavgiationUtils'; +import { isCurrentPath, normalizePath, serializeFolderPath } from './scopeNavgiationUtils'; export interface ScopesNavigationTreeLinkProps { subScope?: string; to: string; title: string; id: string; + subScopePath?: string[]; } -export function ScopesNavigationTreeLink({ subScope, to, title, id }: ScopesNavigationTreeLinkProps) { +export function ScopesNavigationTreeLink({ subScope, to, title, id, subScopePath }: ScopesNavigationTreeLinkProps) { const styles = useStyles2(getStyles); const linkIcon = useMemo(() => getLinkIcon(to), [to]); const locPathname = useLocation().pathname; @@ -25,7 +26,7 @@ export function ScopesNavigationTreeLink({ subScope, to, title, id }: ScopesNavi // Ignore query params const isCurrent = isCurrentPath(locPathname, to); - const handleClick = (e: React.MouseEvent) => { + const handleClick = async (e: React.MouseEvent) => { if (subScope) { e.preventDefault(); // Prevent default Link navigation @@ -39,11 +40,18 @@ export function ScopesNavigationTreeLink({ subScope, to, title, id }: ScopesNavi const searchParams = new URLSearchParams(url.search); if (!currentNavigationScope && currentScope) { searchParams.set('navigation_scope', currentScope); - services?.scopesDashboardsService?.setNavigationScope(currentScope); + await services?.scopesDashboardsService?.setNavigationScope( + currentScope, + undefined, + subScopePath && subScopePath.length > 0 ? subScopePath : undefined + ); } // Update query params with the new subScope searchParams.set('scopes', subScope); + + // Set nav_scope_path to the subScopePath + searchParams.set('nav_scope_path', subScopePath ? serializeFolderPath(subScopePath) : ''); // Remove scope_node and scope_parent since we're changing to a subScope searchParams.delete('scope_node'); searchParams.delete('scope_parent'); diff --git a/public/app/features/scopes/dashboards/scopeNavgiationUtils.test.ts b/public/app/features/scopes/dashboards/scopeNavgiationUtils.test.ts index ec927a3dc92..bd441b6088b 100644 --- a/public/app/features/scopes/dashboards/scopeNavgiationUtils.test.ts +++ b/public/app/features/scopes/dashboards/scopeNavgiationUtils.test.ts @@ -1,4 +1,11 @@ -import { getDashboardPathForComparison, isCurrentPath } from './scopeNavgiationUtils'; +import { + buildSubScopePath, + deserializeFolderPath, + getDashboardPathForComparison, + isCurrentPath, + serializeFolderPath, +} from './scopeNavgiationUtils'; +import { SuggestedNavigationsFoldersMap } from './types'; describe('scopeNavgiationUtils', () => { it('should return the correct path for a dashboard', () => { @@ -28,4 +35,194 @@ describe('scopeNavgiationUtils', () => { expect(isCurrentPath('/d/dashboardId/slug', '/d/dashboardId#hash')).toBe(true); expect(isCurrentPath('/d/dashboardId', '/d/dashboardId#hash')).toBe(true); }); + + describe('deserializeFolderPath', () => { + it('should return empty array for empty string', () => { + expect(deserializeFolderPath('')).toEqual([]); + }); + + it('should parse a simple comma-separated string', () => { + expect(deserializeFolderPath('mimir,loki')).toEqual(['mimir', 'loki']); + }); + + it('should handle single value', () => { + expect(deserializeFolderPath('mimir')).toEqual(['mimir']); + }); + + it('should trim whitespace around values', () => { + expect(deserializeFolderPath(' mimir , loki ')).toEqual(['mimir', 'loki']); + }); + + it('should handle URL-encoded strings', () => { + expect(deserializeFolderPath(encodeURIComponent('mimir,loki'))).toEqual(['mimir', 'loki']); + }); + + it('should handle URL-encoded strings with special characters', () => { + expect(deserializeFolderPath(encodeURIComponent('folder one,folder two'))).toEqual(['folder one', 'folder two']); + }); + + it('should fallback to split without decoding if decodeURIComponent fails', () => { + // Invalid URI sequence that would cause decodeURIComponent to throw + const invalidUri = '%E0%A4%A'; + expect(deserializeFolderPath(invalidUri)).toEqual(['%E0%A4%A']); + }); + }); + + describe('serializeFolderPath', () => { + it('should return empty string for empty array', () => { + expect(serializeFolderPath([])).toBe(''); + }); + + it('should serialize a simple array', () => { + expect(serializeFolderPath(['mimir', 'loki'])).toBe(encodeURIComponent('mimir,loki')); + }); + + it('should handle single value', () => { + expect(serializeFolderPath(['mimir'])).toBe('mimir'); + }); + + it('should handle values with spaces', () => { + expect(serializeFolderPath(['folder one', 'folder two'])).toBe(encodeURIComponent('folder one,folder two')); + }); + + it('should return empty string for null/undefined input', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(serializeFolderPath(null as any)).toBe(''); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + expect(serializeFolderPath(undefined as any)).toBe(''); + }); + }); + + describe('serializeFolderPath and deserializeFolderPath round-trip', () => { + it('should round-trip simple paths', () => { + const original = ['mimir', 'loki']; + const serialized = serializeFolderPath(original); + const deserialized = deserializeFolderPath(serialized); + expect(deserialized).toEqual(original); + }); + + it('should round-trip paths with spaces', () => { + const original = ['folder one', 'folder two']; + const serialized = serializeFolderPath(original); + const deserialized = deserializeFolderPath(serialized); + expect(deserialized).toEqual(original); + }); + }); + + describe('buildSubScopePath', () => { + it('should return undefined when folders is empty', () => { + const folders: SuggestedNavigationsFoldersMap = {}; + expect(buildSubScopePath('mimir', folders)).toBeUndefined(); + }); + + it('should find subScope at root level', () => { + const folders: SuggestedNavigationsFoldersMap = { + 'Mimir Dashboards': { + title: 'Mimir Dashboards', + expanded: false, + folders: {}, + suggestedNavigations: {}, + subScopeName: 'mimir', + }, + }; + expect(buildSubScopePath('mimir', folders)).toEqual(['Mimir Dashboards']); + }); + + it('should find subScope in nested folders', () => { + const folders: SuggestedNavigationsFoldersMap = { + '': { + title: '', + expanded: true, + folders: { + 'Parent Folder': { + title: 'Parent Folder', + expanded: false, + folders: { + 'Mimir Dashboards': { + title: 'Mimir Dashboards', + expanded: false, + folders: {}, + suggestedNavigations: {}, + subScopeName: 'mimir', + }, + }, + suggestedNavigations: {}, + }, + }, + suggestedNavigations: {}, + }, + }; + expect(buildSubScopePath('mimir', folders)).toEqual(['', 'Parent Folder', 'Mimir Dashboards']); + }); + + it('should return undefined when subScope is not found', () => { + const folders: SuggestedNavigationsFoldersMap = { + '': { + title: '', + expanded: true, + folders: { + 'Loki Dashboards': { + title: 'Loki Dashboards', + expanded: false, + folders: {}, + suggestedNavigations: {}, + subScopeName: 'loki', + }, + }, + suggestedNavigations: {}, + }, + }; + expect(buildSubScopePath('mimir', folders)).toBeUndefined(); + }); + + it('should return first match when multiple folders have the same subScope', () => { + const folders: SuggestedNavigationsFoldersMap = { + 'Mimir Dashboards': { + title: 'Mimir Dashboards', + expanded: false, + folders: {}, + suggestedNavigations: {}, + subScopeName: 'mimir', + }, + 'Mimir Overview': { + title: 'Mimir Overview', + expanded: false, + folders: {}, + suggestedNavigations: {}, + subScopeName: 'mimir', + }, + }; + // Should return the first one found (order depends on Object.entries) + const result = buildSubScopePath('mimir', folders); + expect(result).toBeDefined(); + expect(result?.length).toBe(1); + }); + + it('should find deeply nested subScope', () => { + const folders: SuggestedNavigationsFoldersMap = { + level1: { + title: 'Level 1', + expanded: true, + folders: { + level2: { + title: 'Level 2', + expanded: true, + folders: { + level3: { + title: 'Level 3', + expanded: false, + folders: {}, + suggestedNavigations: {}, + subScopeName: 'deep-scope', + }, + }, + suggestedNavigations: {}, + }, + }, + suggestedNavigations: {}, + }, + }; + expect(buildSubScopePath('deep-scope', folders)).toEqual(['level1', 'level2', 'level3']); + }); + }); }); diff --git a/public/app/features/scopes/dashboards/scopeNavgiationUtils.ts b/public/app/features/scopes/dashboards/scopeNavgiationUtils.ts index fe6d458a312..9fa4e2e05c7 100644 --- a/public/app/features/scopes/dashboards/scopeNavgiationUtils.ts +++ b/public/app/features/scopes/dashboards/scopeNavgiationUtils.ts @@ -1,3 +1,5 @@ +import { SuggestedNavigationsFoldersMap } from './types'; + // Helper function to get the base path for a dashboard URL for comparison purposes. // e.g., /d/dashboardId/slug -> /d/dashboardId // /d/dashboardId -> /d/dashboardId @@ -5,12 +7,63 @@ export function getDashboardPathForComparison(pathname: string): string { return pathname.split('/').slice(0, 3).join('/'); } +/** + * Finds the path to a folder with the given subScopeName by searching recursively. + * @param subScope - The subScope name to find + * @param folders - The root folder structure to search + * @returns Array representing the path to the folder, or undefined if not found + */ +export function buildSubScopePath(subScope: string, folders: SuggestedNavigationsFoldersMap): string[] | undefined { + function findPath(currentFolders: SuggestedNavigationsFoldersMap, currentPath: string[]): string[] | undefined { + for (const [key, folder] of Object.entries(currentFolders)) { + const newPath = [...currentPath, key]; + if (folder.subScopeName === subScope) { + return newPath; + } + // Search in nested folders + const nestedPath = findPath(folder.folders, newPath); + if (nestedPath) { + return nestedPath; + } + } + return undefined; + } + + return findPath(folders, []); +} + export function normalizePath(path: string): string { // Remove query + hash + trailing slash (except root) const noQuery = path.split('?')[0].split('#')[0]; return noQuery !== '/' && noQuery.endsWith('/') ? noQuery.slice(0, -1) : noQuery; } +/** + * Deserializes a comma-separated folder path string into an array. + * Handles URL-encoded strings. + */ +export function deserializeFolderPath(navScopePath: string): string[] { + if (!navScopePath) { + return []; + } + try { + const decoded = decodeURIComponent(navScopePath); + return decoded.split(',').map((s) => s.trim()); + } catch { + return navScopePath.split(',').map((s) => s.trim()); + } +} + +/** + * Serializes a folder path array into a comma-separated string. + */ +export function serializeFolderPath(path: string[]): string { + if (!path) { + return ''; + } + return encodeURIComponent(path.join(',')); +} + // Pathname comes from location.pathname export function isCurrentPath(pathname: string, to: string): boolean { const isDashboard = to.startsWith('/d/'); diff --git a/public/app/features/scopes/selector/ScopesSelectorService.test.ts b/public/app/features/scopes/selector/ScopesSelectorService.test.ts index 673046adc0f..58ddb31a92f 100644 --- a/public/app/features/scopes/selector/ScopesSelectorService.test.ts +++ b/public/app/features/scopes/selector/ScopesSelectorService.test.ts @@ -444,7 +444,7 @@ describe('ScopesSelectorService', () => { await service.selectScope('test-scope-node'); await service.apply(); await service.removeAllScopes(); - expect(dashboardsService.setNavigationScope).toHaveBeenCalledWith(undefined); + expect(dashboardsService.setNavigationScope).toHaveBeenCalledWith(undefined, undefined, undefined); }); }); diff --git a/public/app/features/scopes/selector/ScopesSelectorService.ts b/public/app/features/scopes/selector/ScopesSelectorService.ts index 17f36639f59..6f2bf86b7e0 100644 --- a/public/app/features/scopes/selector/ScopesSelectorService.ts +++ b/public/app/features/scopes/selector/ScopesSelectorService.ts @@ -405,7 +405,7 @@ export class ScopesSelectorService extends ScopesServiceBase { this.applyScopes([], false); - this.dashboardsService.setNavigationScope(undefined); + this.dashboardsService.setNavigationScope(undefined, undefined, undefined); }; private addRecentScopes = (scopes: Scope[], parentNode?: ScopeNode, scopeNodeId?: string) => { From f63c2cb2dda408426d4f48640e74e8395d3af98f Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:42:47 +0100 Subject: [PATCH 029/297] Scopes: Don't use redirect if you're on an active scope navigation (#115149) * Don't use redirectUrl if we are on an active scope navigation * Remove superflous test --- devenv/scopes/scopes-config.yaml | 6 ++ devenv/scopes/scopes.go | 7 ++ .../dashboard-cujs/scope-redirect.spec.ts | 69 ++++++++++++------- e2e-playwright/utils/scope-helpers.ts | 56 ++++++++++++++- e2e-playwright/utils/scopes.ts | 18 +++++ .../scopes/selector/ScopesSelectorService.ts | 26 ++++--- 6 files changed, 146 insertions(+), 36 deletions(-) diff --git a/devenv/scopes/scopes-config.yaml b/devenv/scopes/scopes-config.yaml index 54842e5818e..36fd6645fa9 100644 --- a/devenv/scopes/scopes-config.yaml +++ b/devenv/scopes/scopes-config.yaml @@ -83,6 +83,12 @@ tree: nodeType: leaf linkId: test-case-2 linkType: scope + test-case-redirect: + title: Test case with redirect + nodeType: leaf + linkId: shoe-org + linkType: scope + redirectPath: /d/dcb9f5e9-8066-4397-889e-864b99555dbb #Reliability dashboard clusters: title: Clusters nodeType: container diff --git a/devenv/scopes/scopes.go b/devenv/scopes/scopes.go index 1f8a698ccda..335540cbef5 100644 --- a/devenv/scopes/scopes.go +++ b/devenv/scopes/scopes.go @@ -67,10 +67,12 @@ type ScopeFilterConfig struct { type TreeNode struct { Title string `yaml:"title"` SubTitle string `yaml:"subTitle,omitempty"` + Description string `yaml:"description,omitempty"` NodeType string `yaml:"nodeType"` LinkID string `yaml:"linkId,omitempty"` LinkType string `yaml:"linkType,omitempty"` DisableMultiSelect bool `yaml:"disableMultiSelect,omitempty"` + RedirectPath string `yaml:"redirectPath,omitempty"` Children map[string]TreeNode `yaml:"children,omitempty"` } @@ -259,6 +261,7 @@ func (c *Client) createScopeNode(name string, node TreeNode, parentName string) spec := v0alpha1.ScopeNodeSpec{ Title: node.Title, SubTitle: node.SubTitle, + Description: node.Description, NodeType: nodeType, DisableMultiSelect: node.DisableMultiSelect, } @@ -272,6 +275,10 @@ func (c *Client) createScopeNode(name string, node TreeNode, parentName string) spec.LinkType = linkType } + if node.RedirectPath != "" { + spec.RedirectPath = node.RedirectPath + } + resource := v0alpha1.ScopeNode{ TypeMeta: metav1.TypeMeta{ APIVersion: apiVersion, diff --git a/e2e-playwright/dashboard-cujs/scope-redirect.spec.ts b/e2e-playwright/dashboard-cujs/scope-redirect.spec.ts index c0e6e55b5b0..952e8a3da63 100644 --- a/e2e-playwright/dashboard-cujs/scope-redirect.spec.ts +++ b/e2e-playwright/dashboard-cujs/scope-redirect.spec.ts @@ -8,6 +8,7 @@ test.use({ scopeFilters: true, groupByVariable: true, reloadDashboardsOnParamsChange: true, + useScopesNavigationEndpoint: true, }, }); @@ -61,31 +62,6 @@ test.describe('Scope Redirect Functionality', () => { }); }); - test('should fall back to scope navigation when no redirectUrl', async ({ page, gotoDashboardPage }) => { - const scopes = testScopesWithRedirect(); - - await test.step('Navigate to dashboard and open scopes selector', async () => { - await gotoDashboardPage({ uid: 'cuj-dashboard-1' }); - await openScopesSelector(page, scopes); - }); - - await test.step('Select scope without redirectUrl', async () => { - // Select the scope without redirectUrl directly - await selectScope(page, 'sn-redirect-fallback', scopes[1]); - }); - - await test.step('Apply scopes and verify fallback behavior', async () => { - await applyScopes(page, [scopes[1]]); - - // Should stay on current dashboard since no redirectUrl is provided - // The scope navigation fallback should not redirect (as per existing behavior) - await expect(page).toHaveURL(/\/d\/cuj-dashboard-1/); - - // Verify the scope was applied - await expect(page).toHaveURL(/scopes=scope-sn-redirect-fallback/); - }); - }); - test('should not redirect when reloading page on dashboard not in dashboard list', async ({ page, gotoDashboardPage, @@ -171,4 +147,47 @@ test.describe('Scope Redirect Functionality', () => { await expect(page).not.toHaveURL(/scopes=/); }); }); + + test('should not redirect to redirectPath when on active scope navigation', async ({ page, gotoDashboardPage }) => { + const scopes = testScopesWithRedirect(); + + await test.step('Set up scope navigation to dashboard-1', async () => { + // First, apply a scope that creates scope navigation to dashboard-1 (without redirectPath) + await gotoDashboardPage({ uid: 'cuj-dashboard-1' }); + await openScopesSelector(page, scopes); + await selectScope(page, 'sn-redirect-setup', scopes[2]); + await applyScopes(page, [scopes[2]]); + + // Verify we're on dashboard-1 with the scope applied + await expect(page).toHaveURL(/\/d\/cuj-dashboard-1/); + await expect(page).toHaveURL(/scopes=scope-sn-redirect-setup/); + }); + + await test.step('Navigate to dashboard-1 to be on active scope navigation', async () => { + // Navigate to dashboard-1 which is now a scope navigation target + await gotoDashboardPage({ + uid: 'cuj-dashboard-1', + queryParams: new URLSearchParams({ scopes: 'scope-sn-redirect-setup' }), + }); + + // Verify we're on dashboard-1 + await expect(page).toHaveURL(/\/d\/cuj-dashboard-1/); + }); + + await test.step('Apply scope with redirectPath and verify no redirect', async () => { + // Now apply a different scope that has redirectPath + // Since we're on an active scope navigation, it should NOT redirect + await openScopesSelector(page, scopes); + await selectScope(page, 'sn-redirect-with-navigation', scopes[3]); + await applyScopes(page, [scopes[3]]); + + // Verify the new scope was applied + await expect(page).toHaveURL(/scopes=scope-sn-redirect-with-navigation/); + + // Since we're already on the active scope navigation (dashboard-1), + // we should NOT redirect to redirectPath (dashboard-3) + await expect(page).toHaveURL(/\/d\/cuj-dashboard-1/); + await expect(page).not.toHaveURL(/\/d\/cuj-dashboard-3/); + }); + }); }); diff --git a/e2e-playwright/utils/scope-helpers.ts b/e2e-playwright/utils/scope-helpers.ts index 749577ecb05..fc88a79d8fa 100644 --- a/e2e-playwright/utils/scope-helpers.ts +++ b/e2e-playwright/utils/scope-helpers.ts @@ -156,13 +156,18 @@ export async function applyScopes(page: Page, scopes?: TestScope[]) { return; } - const url: string = + const dashboardBindingsUrl: string = '**/apis/scope.grafana.app/v0alpha1/namespaces/*/find/scope_dashboard_bindings?' + scopes.map((scope) => `scope=scope-${scope.name}`).join('&'); + const scopeNavigationsUrl: string = + '**/apis/scope.grafana.app/v0alpha1/namespaces/*/find/scope_navigations?' + + scopes.map((scope) => `scope=scope-${scope.name}`).join('&'); + const groups: string[] = ['Most relevant', 'Dashboards', 'Something else', '']; - await page.route(url, async (route) => { + // Mock scope_dashboard_bindings endpoint + await page.route(dashboardBindingsUrl, async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', @@ -215,7 +220,52 @@ export async function applyScopes(page: Page, scopes?: TestScope[]) { }); }); - const responsePromise = page.waitForResponse((response) => response.url().includes(`/find/scope_dashboard_bindings`)); + // Mock scope_navigations endpoint + await page.route(scopeNavigationsUrl, async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + apiVersion: 'scope.grafana.app/v0alpha1', + items: scopes.flatMap((scope) => { + const navigations: Array<{ + kind: string; + apiVersion: string; + metadata: { name: string; resourceVersion: string; creationTimestamp: string }; + spec: { url: string; scope: string }; + status: { title: string }; + }> = []; + + // Create a scope navigation if dashboardUid is provided + if (scope.dashboardUid && scope.addLinks) { + navigations.push({ + kind: 'ScopeNavigation', + apiVersion: 'scope.grafana.app/v0alpha1', + metadata: { + name: `scope-${scope.name}-nav`, + resourceVersion: '1', + creationTimestamp: 'stamp', + }, + spec: { + url: `/d/${scope.dashboardUid}`, + scope: `scope-${scope.name}`, + }, + status: { + title: scope.dashboardTitle ?? scope.title, + }, + }); + } + + return navigations; + }), + }), + }); + }); + + const responsePromise = page.waitForResponse( + (response) => + response.url().includes(`/find/scope_dashboard_bindings`) || response.url().includes(`/find/scope_navigations`) + ); const scopeRequestPromises: Array> = []; for (const scope of scopes) { diff --git a/e2e-playwright/utils/scopes.ts b/e2e-playwright/utils/scopes.ts index c5b141d4eb7..73a29a5da95 100644 --- a/e2e-playwright/utils/scopes.ts +++ b/e2e-playwright/utils/scopes.ts @@ -124,5 +124,23 @@ export const testScopesWithRedirect = (): TestScope[] => { dashboardTitle: 'CUJ Dashboard 2', addLinks: true, }, + { + name: 'sn-redirect-setup', + title: 'Setup Navigation', + // No redirectPath - used to set up scope navigation to dashboard-1 + filters: [{ key: 'namespace', operator: 'equals', value: 'setup-nav' }], + dashboardUid: 'cuj-dashboard-1', // Creates scope navigation to this dashboard + dashboardTitle: 'CUJ Dashboard 1', + addLinks: true, + }, + { + name: 'sn-redirect-with-navigation', + title: 'Redirect With Navigation', + redirectPath: '/d/cuj-dashboard-3', // Redirect target + filters: [{ key: 'namespace', operator: 'equals', value: 'redirect-with-nav' }], + dashboardUid: 'cuj-dashboard-1', // Creates scope navigation to this dashboard + dashboardTitle: 'CUJ Dashboard 1', + addLinks: true, + }, ]; }; diff --git a/public/app/features/scopes/selector/ScopesSelectorService.ts b/public/app/features/scopes/selector/ScopesSelectorService.ts index 6f2bf86b7e0..abd837c7fb7 100644 --- a/public/app/features/scopes/selector/ScopesSelectorService.ts +++ b/public/app/features/scopes/selector/ScopesSelectorService.ts @@ -372,13 +372,7 @@ export class ScopesSelectorService extends ScopesServiceBase { - // Check if the selected scope has a redirect path - if (scopeNode && scopeNode.spec.redirectPath && typeof scopeNode.spec.redirectPath === 'string') { - locationService.push(scopeNode.spec.redirectPath); - return; - } - - // Redirect to first scopeNavigation if current URL isn't a scopeNavigation + // Check if we are currently on an active scope navigation const currentPath = locationService.getLocation().pathname; const activeScopeNavigation = this.dashboardsService.state.scopeNavigations.find((s) => { if (!('url' in s.spec) || typeof s.spec.url !== 'string') { @@ -387,6 +381,20 @@ export class ScopesSelectorService extends ScopesServiceBase 0) { // Redirect to the first available scopeNavigation const firstScopeNavigation = this.dashboardsService.state.scopeNavigations[0]; @@ -396,7 +404,9 @@ export class ScopesSelectorService extends ScopesServiceBase Date: Thu, 11 Dec 2025 18:05:21 +0100 Subject: [PATCH 030/297] Add new glossary word (#115070) * Docs: Add grafanactl term to glossary * Edit to adapt to Glossary def length * Fix * Real fix * Fix link --------- Co-authored-by: Jack Baldry --- docs/sources/fundamentals/glossary/index.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/sources/fundamentals/glossary/index.md b/docs/sources/fundamentals/glossary/index.md index eee83a7f2de..428764b4ae5 100644 --- a/docs/sources/fundamentals/glossary/index.md +++ b/docs/sources/fundamentals/glossary/index.md @@ -83,6 +83,11 @@ This topic lists words and abbreviations that are commonly used in the Grafana d A commonly-used visualization that displays data as points, lines, or bars. + + grafanactl + + A command-line tool that enables users to authenticate, manage multiple environments, and perform administrative tasks through Grafana's REST API. + mixin From d83b216a32431b2dda92a8bf4007c3a655e06ef7 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Thu, 11 Dec 2025 17:56:40 +0000 Subject: [PATCH 031/297] FS: Fix rendering of public dashboards in MT frontend service (#115162) * pass publicDashboardAccessToken to ST backend via bootdata * slightly cleaner * slightly tidy up go templating * add HandleView middleware --- pkg/api/api.go | 9 +++++++++ pkg/services/frontend/frontend_service.go | 6 ++++++ pkg/services/frontend/index.go | 6 +++++- pkg/services/frontend/index.html | 9 ++++++++- public/app/features/dashboard/routes.ts | 1 + 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 88f432c206c..da1efa5067f 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -187,6 +187,15 @@ func (hs *HTTPServer) registerRoutes() { publicdashboardsapi.CountPublicDashboardRequest(), hs.Index, ) + + r.Get("/bootdata/:accessToken", + reqNoAuth, + hs.PublicDashboardsApi.Middleware.HandleView, + publicdashboardsapi.SetPublicDashboardAccessToken, + publicdashboardsapi.SetPublicDashboardOrgIdOnContext(hs.PublicDashboardsApi.PublicDashboardService), + publicdashboardsapi.CountPublicDashboardRequest(), + hs.GetBootdata, + ) } r.Get("/explore", authorize(ac.EvalPermission(ac.ActionDatasourcesExplore)), hs.Index) diff --git a/pkg/services/frontend/frontend_service.go b/pkg/services/frontend/frontend_service.go index f730bfba376..943509024d3 100644 --- a/pkg/services/frontend/frontend_service.go +++ b/pkg/services/frontend/frontend_service.go @@ -22,6 +22,7 @@ import ( fswebassets "github.com/grafana/grafana/pkg/services/frontend/webassets" "github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/licensing" + publicdashboardsapi "github.com/grafana/grafana/pkg/services/publicdashboards/api" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" ) @@ -164,6 +165,11 @@ func (s *frontendService) registerRoutes(m *web.Mux) { // uses cache busting to ensure requests aren't cached. s.routeGet(m, "/-/fe-boot-error", s.handleBootError) + s.routeGet(m, "/public-dashboards/:accessToken", + publicdashboardsapi.SetPublicDashboardAccessToken, + s.index.HandleRequest, + ) + // All other requests return index.html s.routeGet(m, "/*", s.index.HandleRequest) } diff --git a/pkg/services/frontend/index.go b/pkg/services/frontend/index.go index 22d04234a36..e87ca894d20 100644 --- a/pkg/services/frontend/index.go +++ b/pkg/services/frontend/index.go @@ -45,6 +45,8 @@ type IndexViewData struct { // Nonce is a cryptographic identifier for use with Content Security Policy. Nonce string + + PublicDashboardAccessToken string } // Templates setup. @@ -138,9 +140,12 @@ func (p *IndexProvider) HandleRequest(writer http.ResponseWriter, request *http. return } + reqCtx := contexthandler.FromContext(ctx) + // TODO -- restructure so the static stuff is under one variable and the rest is dynamic data := p.data // copy everything data.Nonce = nonce + data.PublicDashboardAccessToken = reqCtx.PublicDashboardAccessToken if data.CSPEnabled { data.CSPContent = middleware.ReplacePolicyVariables(p.data.CSPContent, p.data.AppSubUrl, data.Nonce) @@ -150,7 +155,6 @@ func (p *IndexProvider) HandleRequest(writer http.ResponseWriter, request *http. writer.Header().Set("Content-Security-Policy-Report-Only", policy) } - reqCtx := contexthandler.FromContext(ctx) p.runIndexDataHooks(reqCtx, &data) writer.Header().Set("Content-Type", "text/html; charset=UTF-8") diff --git a/pkg/services/frontend/index.html b/pkg/services/frontend/index.html index b0364fcbac9..198b8216189 100644 --- a/pkg/services/frontend/index.html +++ b/pkg/services/frontend/index.html @@ -188,6 +188,7 @@ // Wrap in an IIFE to avoid polluting the global scope. Intentionally global-scope properties // are explicitly assigned to the `window` object. (() => { + const publicDashboardAccessToken = [[.PublicDashboardAccessToken]] // Grafana can only fail to load once // However, it can fail to load in multiple different places // To avoid double reporting the error, we use this boolean to check if we've already failed @@ -271,9 +272,15 @@ async function fetchBootData() { const queryParams = new URLSearchParams(window.location.search); + let path = '/bootdata'; + // call a special bootdata url with the public access token + // this is needed to set the access token and correct org for public dashboards on the ST backend + if (publicDashboardAccessToken) { + path += `/${publicDashboardAccessToken}`; + } // pass the search params through to the bootdata request // this allows for overriding the theme/language etc - const bootDataUrl = new URL('/bootdata', window.location.origin); + const bootDataUrl = new URL(path, window.location.origin); for (const [key, value] of queryParams.entries()) { bootDataUrl.searchParams.append(key, value); } diff --git a/public/app/features/dashboard/routes.ts b/public/app/features/dashboard/routes.ts index 84e7d514b99..57702d1a35d 100644 --- a/public/app/features/dashboard/routes.ts +++ b/public/app/features/dashboard/routes.ts @@ -24,6 +24,7 @@ export const getPublicDashboardRoutes = (): RouteDescriptor[] => { { path: '/public-dashboards/:accessToken', pageClass: 'page-dashboard', + allowAnonymous: true, routeName: DashboardRoutes.Public, chromeless: true, component: SafeDynamicImport( From 0c264b7a5fa5a68157c60b7139fdafe425a65001 Mon Sep 17 00:00:00 2001 From: Misi Date: Thu, 11 Dec 2025 19:54:48 +0100 Subject: [PATCH 032/297] IAM: Add user search endpoint (#114542) * wip: initial changes, api registration * wip * LegacySearch working with sorting * Revert mapper change for now * Clean up * Cleanup, add integration tests * Improve tests * OpenAPI def regen * Use wildcard search, fix lastSeenAt handling, add lastSeenAtAge * Add missing files * Fix merge * Fixes * Add tests, regen openapi def * Address feedback * Address feedback batch 2 * Chores * regen openapidef * Address feedback * Add tests for paging * gen apis * Revert go.mod, go.sum. go.work.sum * Fix + remove extra tracer parameter --- apps/iam/kinds/manifest.cue | 36 ++ apps/iam/kinds/user.cue | 3 + ...etsearchusers_request_params_object_gen.go | 33 + ...getsearchusers_request_params_types_gen.go | 15 + .../getsearchusers_response_types_gen.go | 37 ++ .../pkg/apis/iam/v0alpha1/user_client_gen.go | 19 + .../pkg/apis/iam/v0alpha1/user_object_gen.go | 31 +- .../pkg/apis/iam/v0alpha1/user_status_gen.go | 33 +- .../pkg/apis/iam/v0alpha1/zz_openapi_gen.go | 230 ++++--- apps/iam/pkg/apis/iam_manifest.go | 176 ++++++ .../rtkq/iam/v0alpha1/endpoints.gen.ts | 31 + pkg/registry/apis/iam/authorizer.go | 4 +- pkg/registry/apis/iam/models.go | 9 +- pkg/registry/apis/iam/register.go | 32 +- pkg/registry/apis/iam/user/legacy_search.go | 180 ++++-- .../apis/iam/user/legacy_search_fake.go | 8 +- .../apis/iam/user/legacy_search_test.go | 44 +- pkg/registry/apis/iam/user/search.go | 399 ++++++++++++ pkg/registry/apis/iam/user/search_test.go | 169 ++++++ pkg/registry/apis/iam/user/store.go | 39 +- pkg/registry/apis/iam/user/validate.go | 4 +- pkg/registry/apis/iam/user/validate_test.go | 12 +- pkg/server/wire_gen.go | 4 +- .../unified/search/builders/document_test.go | 5 +- .../user-with-last-seen-at-and-role-out.json | 16 + .../doc/user-with-last-seen-at-and-role.json | 14 + ...son => user-with-login-and-email-out.json} | 8 +- ...il.json => user-with-login-and-email.json} | 2 +- ...out.json => user-with-login-only-out.json} | 8 +- ...in-only.json => user-with-login-only.json} | 2 +- pkg/storage/unified/search/builders/user.go | 32 +- .../apis/iam/user_search_integration_test.go | 568 ++++++++++++++++++ .../iam.grafana.app-v0alpha1.json | 318 ++++++++-- 33 files changed, 2222 insertions(+), 299 deletions(-) create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/getsearchusers_request_params_object_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/getsearchusers_request_params_types_gen.go create mode 100644 apps/iam/pkg/apis/iam/v0alpha1/getsearchusers_response_types_gen.go create mode 100644 pkg/registry/apis/iam/user/search.go create mode 100644 pkg/registry/apis/iam/user/search_test.go create mode 100644 pkg/storage/unified/search/builders/testdata/doc/user-with-last-seen-at-and-role-out.json create mode 100644 pkg/storage/unified/search/builders/testdata/doc/user-with-last-seen-at-and-role.json rename pkg/storage/unified/search/builders/testdata/doc/{user-user-with-login-and-email-out.json => user-with-login-and-email-out.json} (55%) rename pkg/storage/unified/search/builders/testdata/doc/{user-user-with-login-and-email.json => user-with-login-and-email.json} (79%) rename pkg/storage/unified/search/builders/testdata/doc/{user-user-with-login-only-out.json => user-with-login-only-out.json} (51%) rename pkg/storage/unified/search/builders/testdata/doc/{user-user-with-login-only.json => user-with-login-only.json} (77%) create mode 100644 pkg/tests/apis/iam/user_search_integration_test.go diff --git a/apps/iam/kinds/manifest.cue b/apps/iam/kinds/manifest.cue index c6f609cbe15..0cb8d534b2f 100644 --- a/apps/iam/kinds/manifest.cue +++ b/apps/iam/kinds/manifest.cue @@ -22,8 +22,32 @@ v0alpha1: { serviceaccountv0alpha1, externalGroupMappingv0alpha1 ] + routes: { namespaced: { + "/searchUsers": { + "GET": { + request: { + query: { + query?: string + limit?: int64 | 10 + offset?: int64 | 0 + page?: int64 | 1 + } + } + response: { + offset: int64 + totalHits: int64 + hits: [...#UserHit] + queryCost: float64 + maxScore: float64 + } + responseMetadata: { + typeMeta: false + objectMeta: false + } + } + } "/searchTeams": { "GET": { request: { @@ -51,3 +75,15 @@ v0alpha1: { } } } + +#UserHit: { + name: string + title: string + login: string + email: string + role: string + lastSeenAt: int64 + lastSeenAtAge: string + provisioned: bool + score: float64 +} diff --git a/apps/iam/kinds/user.cue b/apps/iam/kinds/user.cue index a67c0949e1f..fc5bb8fb153 100644 --- a/apps/iam/kinds/user.cue +++ b/apps/iam/kinds/user.cue @@ -29,6 +29,9 @@ userv0alpha1: userKind & { // } schema: { spec: v0alpha1.UserSpec + status: { + lastSeenAt: int64 | 0 + } } // TODO: Uncomment when the custom routes implementation is done // routes: { diff --git a/apps/iam/pkg/apis/iam/v0alpha1/getsearchusers_request_params_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/getsearchusers_request_params_object_gen.go new file mode 100644 index 00000000000..3a6c04a1fa0 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/getsearchusers_request_params_object_gen.go @@ -0,0 +1,33 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +import ( + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +type GetSearchUsersRequestParamsObject struct { + metav1.TypeMeta `json:",inline"` + GetSearchUsersRequestParams `json:",inline"` +} + +func NewGetSearchUsersRequestParamsObject() *GetSearchUsersRequestParamsObject { + return &GetSearchUsersRequestParamsObject{} +} + +func (o *GetSearchUsersRequestParamsObject) DeepCopyObject() runtime.Object { + dst := NewGetSearchUsersRequestParamsObject() + o.DeepCopyInto(dst) + return dst +} + +func (o *GetSearchUsersRequestParamsObject) DeepCopyInto(dst *GetSearchUsersRequestParamsObject) { + dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion + dst.TypeMeta.Kind = o.TypeMeta.Kind + dstGetSearchUsersRequestParams := GetSearchUsersRequestParams{} + _ = resource.CopyObjectInto(&dstGetSearchUsersRequestParams, &o.GetSearchUsersRequestParams) +} + +var _ runtime.Object = NewGetSearchUsersRequestParamsObject() diff --git a/apps/iam/pkg/apis/iam/v0alpha1/getsearchusers_request_params_types_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/getsearchusers_request_params_types_gen.go new file mode 100644 index 00000000000..22b73ea80d8 --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/getsearchusers_request_params_types_gen.go @@ -0,0 +1,15 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +type GetSearchUsersRequestParams struct { + Query *string `json:"query,omitempty"` + Limit int64 `json:"limit,omitempty"` + Offset int64 `json:"offset,omitempty"` + Page int64 `json:"page,omitempty"` +} + +// NewGetSearchUsersRequestParams creates a new GetSearchUsersRequestParams object. +func NewGetSearchUsersRequestParams() *GetSearchUsersRequestParams { + return &GetSearchUsersRequestParams{} +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/getsearchusers_response_types_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/getsearchusers_response_types_gen.go new file mode 100644 index 00000000000..d56cbfa3a3c --- /dev/null +++ b/apps/iam/pkg/apis/iam/v0alpha1/getsearchusers_response_types_gen.go @@ -0,0 +1,37 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type UserHit struct { + Name string `json:"name"` + Title string `json:"title"` + Login string `json:"login"` + Email string `json:"email"` + Role string `json:"role"` + LastSeenAt int64 `json:"lastSeenAt"` + LastSeenAtAge string `json:"lastSeenAtAge"` + Provisioned bool `json:"provisioned"` + Score float64 `json:"score"` +} + +// NewUserHit creates a new UserHit object. +func NewUserHit() *UserHit { + return &UserHit{} +} + +// +k8s:openapi-gen=true +type GetSearchUsers struct { + Offset int64 `json:"offset"` + TotalHits int64 `json:"totalHits"` + Hits []UserHit `json:"hits"` + QueryCost float64 `json:"queryCost"` + MaxScore float64 `json:"maxScore"` +} + +// NewGetSearchUsers creates a new GetSearchUsers object. +func NewGetSearchUsers() *GetSearchUsers { + return &GetSearchUsers{ + Hits: []UserHit{}, + } +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/user_client_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/user_client_gen.go index 665df84327e..bd7af9b3361 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/user_client_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/user_client_gen.go @@ -4,6 +4,7 @@ import ( "context" "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type UserClient struct { @@ -75,6 +76,24 @@ func (c *UserClient) Patch(ctx context.Context, identifier resource.Identifier, return c.client.Patch(ctx, identifier, req, opts) } +func (c *UserClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus UserStatus, opts resource.UpdateOptions) (*User, error) { + return c.client.Update(ctx, &User{ + TypeMeta: metav1.TypeMeta{ + Kind: UserKind().Kind(), + APIVersion: GroupVersion.Identifier(), + }, + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: opts.ResourceVersion, + Namespace: identifier.Namespace, + Name: identifier.Name, + }, + Status: newStatus, + }, resource.UpdateOptions{ + Subresource: "status", + ResourceVersion: opts.ResourceVersion, + }) +} + func (c *UserClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { return c.client.Delete(ctx, identifier, opts) } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/user_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/user_object_gen.go index bd3bf8fae0e..f4bc19dccbb 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/user_object_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/user_object_gen.go @@ -21,11 +21,14 @@ type User struct { // Spec is the spec of the User Spec UserSpec `json:"spec" yaml:"spec"` + + Status UserStatus `json:"status" yaml:"status"` } func NewUser() *User { return &User{ - Spec: *NewUserSpec(), + Spec: *NewUserSpec(), + Status: *NewUserStatus(), } } @@ -43,11 +46,15 @@ func (o *User) SetSpec(spec any) error { } func (o *User) GetSubresources() map[string]any { - return map[string]any{} + return map[string]any{ + "status": o.Status, + } } func (o *User) GetSubresource(name string) (any, bool) { switch name { + case "status": + return o.Status, true default: return nil, false } @@ -55,6 +62,13 @@ func (o *User) GetSubresource(name string) (any, bool) { func (o *User) SetSubresource(name string, value any) error { switch name { + case "status": + cast, ok := value.(UserStatus) + if !ok { + return fmt.Errorf("cannot set status type %#v, not of type UserStatus", value) + } + o.Status = cast + return nil default: return fmt.Errorf("subresource '%s' does not exist", name) } @@ -226,6 +240,7 @@ func (o *User) DeepCopyInto(dst *User) { dst.TypeMeta.Kind = o.TypeMeta.Kind o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) o.Spec.DeepCopyInto(&dst.Spec) + o.Status.DeepCopyInto(&dst.Status) } // Interface compliance compile-time check @@ -297,3 +312,15 @@ func (s *UserSpec) DeepCopy() *UserSpec { func (s *UserSpec) DeepCopyInto(dst *UserSpec) { resource.CopyObjectInto(dst, s) } + +// DeepCopy creates a full deep copy of UserStatus +func (s *UserStatus) DeepCopy() *UserStatus { + cpy := &UserStatus{} + s.DeepCopyInto(cpy) + return cpy +} + +// DeepCopyInto deep copies UserStatus into another UserStatus object +func (s *UserStatus) DeepCopyInto(dst *UserStatus) { + resource.CopyObjectInto(dst, s) +} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/user_status_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/user_status_gen.go index 34a138f59ce..dd0f3e46767 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/user_status_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/user_status_gen.go @@ -2,43 +2,12 @@ package v0alpha1 -// +k8s:openapi-gen=true -type UserstatusOperatorState struct { - // lastEvaluation is the ResourceVersion last evaluated - LastEvaluation string `json:"lastEvaluation"` - // state describes the state of the lastEvaluation. - // It is limited to three possible states for machine evaluation. - State UserStatusOperatorStateState `json:"state"` - // descriptiveState is an optional more descriptive state field which has no requirements on format - DescriptiveState *string `json:"descriptiveState,omitempty"` - // details contains any extra information that is operator-specific - Details map[string]interface{} `json:"details,omitempty"` -} - -// NewUserstatusOperatorState creates a new UserstatusOperatorState object. -func NewUserstatusOperatorState() *UserstatusOperatorState { - return &UserstatusOperatorState{} -} - // +k8s:openapi-gen=true type UserStatus struct { - // operatorStates is a map of operator ID to operator state evaluations. - // Any operator which consumes this kind SHOULD add its state evaluation information to this field. - OperatorStates map[string]UserstatusOperatorState `json:"operatorStates,omitempty"` - // additionalFields is reserved for future use - AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` + LastSeenAt int64 `json:"lastSeenAt"` } // NewUserStatus creates a new UserStatus object. func NewUserStatus() *UserStatus { return &UserStatus{} } - -// +k8s:openapi-gen=true -type UserStatusOperatorStateState string - -const ( - UserStatusOperatorStateStateSuccess UserStatusOperatorStateState = "success" - UserStatusOperatorStateStateInProgress UserStatusOperatorStateState = "in_progress" - UserStatusOperatorStateStateFailed UserStatusOperatorStateState = "failed" -) diff --git a/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go index ece208e8d68..87128d27699 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go @@ -21,6 +21,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GetGroupsBody": schema_pkg_apis_iam_v0alpha1_GetGroupsBody(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GetSearchTeams": schema_pkg_apis_iam_v0alpha1_GetSearchTeams(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GetSearchTeamsBody": schema_pkg_apis_iam_v0alpha1_GetSearchTeamsBody(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GetSearchUsers": schema_pkg_apis_iam_v0alpha1_GetSearchUsers(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRole": schema_pkg_apis_iam_v0alpha1_GlobalRole(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBinding": schema_pkg_apis_iam_v0alpha1_GlobalRoleBinding(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingList": schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingList(ref), @@ -72,10 +73,10 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamStatus": schema_pkg_apis_iam_v0alpha1_TeamStatus(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamstatusOperatorState": schema_pkg_apis_iam_v0alpha1_TeamstatusOperatorState(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.User": schema_pkg_apis_iam_v0alpha1_User(ref), + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserHit": schema_pkg_apis_iam_v0alpha1_UserHit(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserList": schema_pkg_apis_iam_v0alpha1_UserList(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserSpec": schema_pkg_apis_iam_v0alpha1_UserSpec(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserStatus": schema_pkg_apis_iam_v0alpha1_UserStatus(ref), - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserstatusOperatorState": schema_pkg_apis_iam_v0alpha1_UserstatusOperatorState(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping": schema_pkg_apis_iam_v0alpha1_VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping(ref), "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.VersionsV0alpha1RoutesNamespacedSearchTeamsGETResponseTeamHit": schema_pkg_apis_iam_v0alpha1_VersionsV0alpha1RoutesNamespacedSearchTeamsGETResponseTeamHit(ref), } @@ -688,6 +689,62 @@ func schema_pkg_apis_iam_v0alpha1_GetSearchTeamsBody(ref common.ReferenceCallbac } } +func schema_pkg_apis_iam_v0alpha1_GetSearchUsers(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "offset": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "totalHits": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "hits": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserHit"), + }, + }, + }, + }, + }, + "queryCost": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"number"}, + Format: "double", + }, + }, + "maxScore": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"number"}, + Format: "double", + }, + }, + }, + Required: []string{"offset", "totalHits", "hits", "queryCost", "maxScore"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserHit"}, + } +} + func schema_pkg_apis_iam_v0alpha1_GlobalRole(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -2833,12 +2890,94 @@ func schema_pkg_apis_iam_v0alpha1_User(ref common.ReferenceCallback) common.Open Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserSpec"), }, }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserStatus"), + }, + }, }, - Required: []string{"metadata", "spec"}, + Required: []string{"metadata", "spec", "status"}, }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserSpec", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_iam_v0alpha1_UserHit(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "title": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "login": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "email": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "role": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "lastSeenAt": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "lastSeenAtAge": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "provisioned": { + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "score": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"number"}, + Format: "double", + }, + }, + }, + Required: []string{"name", "title", "login", "email", "role", "lastSeenAt", "lastSeenAtAge", "provisioned", "score"}, + }, + }, } } @@ -2965,90 +3104,15 @@ func schema_pkg_apis_iam_v0alpha1_UserStatus(ref common.ReferenceCallback) commo SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ - "operatorStates": { + "lastSeenAt": { SchemaProps: spec.SchemaProps{ - Description: "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserstatusOperatorState"), - }, - }, - }, - }, - }, - "additionalFields": { - SchemaProps: spec.SchemaProps{ - Description: "additionalFields is reserved for future use", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, + Default: 0, + Type: []string{"integer"}, + Format: "int64", }, }, }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserstatusOperatorState"}, - } -} - -func schema_pkg_apis_iam_v0alpha1_UserstatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "lastEvaluation": { - SchemaProps: spec.SchemaProps{ - Description: "lastEvaluation is the ResourceVersion last evaluated", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "state": { - SchemaProps: spec.SchemaProps{ - Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "descriptiveState": { - SchemaProps: spec.SchemaProps{ - Description: "descriptiveState is an optional more descriptive state field which has no requirements on format", - Type: []string{"string"}, - Format: "", - }, - }, - "details": { - SchemaProps: spec.SchemaProps{ - Description: "details contains any extra information that is operator-specific", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - }, - Required: []string{"lastEvaluation", "state"}, + Required: []string{"lastSeenAt"}, }, }, } diff --git a/apps/iam/pkg/apis/iam_manifest.go b/apps/iam/pkg/apis/iam_manifest.go index 0106984f7f2..37552d1bdf3 100644 --- a/apps/iam/pkg/apis/iam_manifest.go +++ b/apps/iam/pkg/apis/iam_manifest.go @@ -261,6 +261,118 @@ var appManifestData = app.ManifestData{ }, }, }, + "/searchUsers": { + Get: &spec3.Operation{ + OperationProps: spec3.OperationProps{ + + OperationId: "getSearchUsers", + + Parameters: []*spec3.Parameter{ + + { + ParameterProps: spec3.ParameterProps{ + Name: "limit", + In: "query", + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{}, + }, + }, + }, + + { + ParameterProps: spec3.ParameterProps{ + Name: "offset", + In: "query", + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{}, + }, + }, + }, + + { + ParameterProps: spec3.ParameterProps{ + Name: "page", + In: "query", + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{}, + }, + }, + }, + + { + ParameterProps: spec3.ParameterProps{ + Name: "query", + In: "query", + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + }, + }, + }, + + Responses: &spec3.Responses{ + ResponsesProps: spec3.ResponsesProps{ + Default: &spec3.Response{ + ResponseProps: spec3.ResponseProps{ + Description: "Default OK response", + Content: map[string]*spec3.MediaType{ + "application/json": { + MediaTypeProps: spec3.MediaTypeProps{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "hits": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + + Ref: spec.MustCreateRef("#/components/schemas/getSearchUsersUserHit"), + }}, + }, + }, + }, + "maxScore": { + SchemaProps: spec.SchemaProps{ + Type: []string{"number"}, + }, + }, + "offset": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + }, + }, + "queryCost": { + SchemaProps: spec.SchemaProps{ + Type: []string{"number"}, + }, + }, + "totalHits": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + }, + }, + }, + Required: []string{ + "offset", + "totalHits", + "hits", + "queryCost", + "maxScore", + }, + }}, + }}, + }, + }, + }, + }}, + }, + }, + }, }, Cluster: map[string]spec3.PathProps{}, Schemas: map[string]spec.Schema{ @@ -303,6 +415,69 @@ var appManifestData = app.ManifestData{ }, }, }, + "getSearchUsersUserHit": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "email": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + "lastSeenAt": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + }, + }, + "lastSeenAtAge": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + "login": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + "provisioned": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "role": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + "score": { + SchemaProps: spec.SchemaProps{ + Type: []string{"number"}, + }, + }, + "title": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + }, + Required: []string{ + "name", + "title", + "login", + "email", + "role", + "lastSeenAt", + "lastSeenAtAge", + "provisioned", + "score", + }, + }, + }, }, }, }, @@ -342,6 +517,7 @@ var customRouteToGoResponseType = map[string]any{ "v0alpha1|Team|groups|GET": v0alpha1.GetGroups{}, "v0alpha1||/searchTeams|GET": v0alpha1.GetSearchTeams{}, + "v0alpha1||/searchUsers|GET": v0alpha1.GetSearchUsers{}, } // ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. diff --git a/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts index 8b0690f57ba..790db19a6e3 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts @@ -165,6 +165,19 @@ const injectedRtkApi = api }), providesTags: ['Search'], }), + getSearchUsers: build.query({ + query: (queryArg) => ({ + url: `/searchUsers`, + params: { + query: queryArg.query, + limit: queryArg.limit, + page: queryArg.page, + offset: queryArg.offset, + sort: queryArg.sort, + }, + }), + providesTags: ['Search'], + }), listServiceAccount: build.query({ query: (queryArg) => ({ url: `/serviceaccounts`, @@ -896,6 +909,18 @@ export type GetSearchTeamsApiArg = { /** page number to start from */ page?: number; }; +export type GetSearchUsersApiResponse = unknown; +export type GetSearchUsersApiArg = { + query?: string; + /** number of results to return */ + limit?: number; + /** page number (starting from 1) */ + page?: number; + /** number of results to skip */ + offset?: number; + /** sortable field */ + sort?: string; +}; export type ListServiceAccountApiResponse = /** status 200 OK */ ServiceAccountList; export type ListServiceAccountApiArg = { /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ @@ -2067,6 +2092,9 @@ export type UserSpec = { role: string; title: string; }; +export type UserStatus = { + lastSeenAt: number; +}; export type User = { /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ apiVersion?: string; @@ -2075,6 +2103,7 @@ export type User = { metadata: ObjectMeta; /** Spec is the spec of the User */ spec: UserSpec; + status: UserStatus; }; export type UserList = { /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ @@ -2120,6 +2149,8 @@ export const { useUpdateExternalGroupMappingMutation, useGetSearchTeamsQuery, useLazyGetSearchTeamsQuery, + useGetSearchUsersQuery, + useLazyGetSearchUsersQuery, useListServiceAccountQuery, useLazyListServiceAccountQuery, useCreateServiceAccountMutation, diff --git a/pkg/registry/apis/iam/authorizer.go b/pkg/registry/apis/iam/authorizer.go index a3997638e1e..026254ebab6 100644 --- a/pkg/registry/apis/iam/authorizer.go +++ b/pkg/registry/apis/iam/authorizer.go @@ -22,6 +22,7 @@ type iamAuthorizer struct { func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient authlib.AccessClient) authorizer.Authorizer { resourceAuthorizer := make(map[string]authorizer.Authorizer) + serviceAuthorizer := gfauthorizer.NewServiceAuthorizer() // Authorizer that allows any authenticated user // To be used when authorization is handled at the storage layer allowAuthorizer := authorizer.AuthorizerFunc(func( @@ -50,8 +51,7 @@ func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient auth resourceAuthorizer[iamv0.UserResourceInfo.GetName()] = authorizer resourceAuthorizer[iamv0.ExternalGroupMappingResourceInfo.GetName()] = authorizer resourceAuthorizer[iamv0.TeamResourceInfo.GetName()] = authorizer - - serviceAuthorizer := gfauthorizer.NewServiceAuthorizer() + resourceAuthorizer["searchUsers"] = serviceAuthorizer resourceAuthorizer["searchTeams"] = serviceAuthorizer return &iamAuthorizer{resourceAuthorizer: resourceAuthorizer} diff --git a/pkg/registry/apis/iam/models.go b/pkg/registry/apis/iam/models.go index b851122ffd9..d47afabee5d 100644 --- a/pkg/registry/apis/iam/models.go +++ b/pkg/registry/apis/iam/models.go @@ -77,10 +77,11 @@ type IdentityAccessManagementAPIBuilder struct { reg prometheus.Registerer logger log.Logger - dual dualwrite.Service - unified resource.ResourceClient - userSearchClient resourcepb.ResourceIndexClient - teamSearch *TeamSearchHandler + dual dualwrite.Service + unified resource.ResourceClient + userSearchClient resourcepb.ResourceIndexClient + userSearchHandler *user.SearchHandler + teamSearch *TeamSearchHandler teamGroupsHandler externalgroupmapping.TeamGroupsHandler diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 85d8bc4e4f1..d6b795f1d2e 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -46,6 +46,7 @@ import ( "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/ssosettings" teamservice "github.com/grafana/grafana/pkg/services/team" legacyuser "github.com/grafana/grafana/pkg/services/user" @@ -76,6 +77,7 @@ func RegisterAPIService( teamGroupsHandlerImpl externalgroupmapping.TeamGroupsHandler, dual dualwrite.Service, unified resource.ResourceClient, + orgService org.Service, userService legacyuser.Service, teamService teamservice.Service, ) (*IdentityAccessManagementAPIBuilder, error) { @@ -114,9 +116,11 @@ func RegisterAPIService( dual: dual, unified: unified, userSearchClient: resource.NewSearchClient(dualwrite.NewSearchAdapter(dual), iamv0.UserResourceInfo.GroupResource(), - unified, user.NewUserLegacySearchClient(userService, tracing), features), + unified, user.NewUserLegacySearchClient(orgService, tracing, cfg), features), teamSearch: NewTeamSearchHandler(tracing, dual, team.NewLegacyTeamSearchClient(teamService), unified, features), } + builder.userSearchHandler = user.NewSearchHandler(tracing, builder.userSearchClient, features, cfg) + apiregistration.RegisterAPI(builder) return builder, nil @@ -510,10 +514,18 @@ func (b *IdentityAccessManagementAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenA func (b *IdentityAccessManagementAPIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.APIRoutes { defs := b.GetOpenAPIDefinitions()(func(path string) spec.Ref { return spec.Ref{} }) - routes := b.teamSearch.GetAPIRoutes(defs) - routes.Namespace = append(routes.Namespace, b.display.GetAPIRoutes(defs).Namespace...) + searchRoutes := make([]*builder.APIRoutes, 0, 2) + if b.userSearchHandler != nil { + searchRoutes = append(searchRoutes, b.userSearchHandler.GetAPIRoutes(defs)) + } - return routes + if b.teamSearch != nil { + searchRoutes = append(searchRoutes, b.teamSearch.GetAPIRoutes(defs)) + } + + routes := []*builder.APIRoutes{b.display.GetAPIRoutes(defs)} + routes = append(routes, searchRoutes...) + return mergeAPIRoutes(routes...) } func (b *IdentityAccessManagementAPIBuilder) GetAuthorizer() authorizer.Authorizer { @@ -621,3 +633,15 @@ func NewLocalStore(resourceInfo utils.ResourceInfo, scheme *runtime.Scheme, defa store, err := grafanaregistry.NewRegistryStore(scheme, resourceInfo, optsGetter) return store, err } + +func mergeAPIRoutes(routes ...*builder.APIRoutes) *builder.APIRoutes { + merged := &builder.APIRoutes{} + for _, r := range routes { + if r == nil { + continue + } + merged.Root = append(merged.Root, r.Root...) + merged.Namespace = append(merged.Namespace, r.Namespace...) + } + return merged +} diff --git a/pkg/registry/apis/iam/user/legacy_search.go b/pkg/registry/apis/iam/user/legacy_search.go index 7fb6c13f7a6..41b54995c38 100644 --- a/pkg/registry/apis/iam/user/legacy_search.go +++ b/pkg/registry/apis/iam/user/legacy_search.go @@ -2,16 +2,22 @@ package user import ( "context" + "encoding/binary" "fmt" "log/slog" "math" + "regexp" + "sort" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/services/user" - res "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/search/model" + "github.com/grafana/grafana/pkg/services/searchusers/sortopts" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" "github.com/grafana/grafana/pkg/storage/unified/search/builders" ) @@ -21,28 +27,36 @@ const ( UserResourceGroup = "iam.grafana.com" ) -var _ resourcepb.ResourceIndexClient = (*UserLegacySearchClient)(nil) +var ( + _ resourcepb.ResourceIndexClient = (*UserLegacySearchClient)(nil) + fieldLogin = fmt.Sprintf("%s%s", resource.SEARCH_FIELD_PREFIX, builders.USER_LOGIN) + fieldEmail = fmt.Sprintf("%s%s", resource.SEARCH_FIELD_PREFIX, builders.USER_EMAIL) + fieldLastSeenAt = fmt.Sprintf("%s%s", resource.SEARCH_FIELD_PREFIX, builders.USER_LAST_SEEN_AT) + fieldRole = fmt.Sprintf("%s%s", resource.SEARCH_FIELD_PREFIX, builders.USER_ROLE) + wildcardsMatcher = regexp.MustCompile(`[\*\?\\]`) +) // UserLegacySearchClient is a client for searching for users in the legacy search engine. type UserLegacySearchClient struct { resourcepb.ResourceIndexClient - userService user.Service - log *slog.Logger - tracer trace.Tracer + orgService org.Service + log *slog.Logger + tracer trace.Tracer + cfg *setting.Cfg } // NewUserLegacySearchClient creates a new UserLegacySearchClient. -func NewUserLegacySearchClient(userService user.Service, tracer trace.Tracer) *UserLegacySearchClient { +func NewUserLegacySearchClient(orgService org.Service, tracer trace.Tracer, cfg *setting.Cfg) *UserLegacySearchClient { return &UserLegacySearchClient{ - userService: userService, - log: slog.Default().With("logger", "legacy-user-search-client"), - tracer: tracer, + orgService: orgService, + log: slog.Default().With("logger", "legacy-user-search-client"), + tracer: tracer, + cfg: cfg, } } // Search searches for users in the legacy search engine. // It only supports exact matching for title, login, or email. -// FIXME: This implementation only supports a single field query and will be extended in the future. func (c *UserLegacySearchClient) Search(ctx context.Context, req *resourcepb.ResourceSearchRequest, _ ...grpc.CallOption) (*resourcepb.ResourceSearchResponse, error) { ctx, span := c.tracer.Start(ctx, "user.Search") defer span.End() @@ -52,21 +66,30 @@ func (c *UserLegacySearchClient) Search(ctx context.Context, req *resourcepb.Res return nil, err } - if req.Limit > 100 { - req.Limit = 100 + if req.Limit > maxLimit { + req.Limit = maxLimit } if req.Limit <= 0 { - req.Limit = 1 + req.Limit = 30 } if req.Page > math.MaxInt32 || req.Page < 0 { return nil, fmt.Errorf("invalid page number: %d", req.Page) } - query := &user.SearchUsersQuery{ - SignedInUser: signedInUser, - Limit: int(req.Limit), - Page: int(req.Page), + if req.Page < 1 { + req.Page = 1 + } + + legacySortOptions := convertToSortOptions(req.SortBy) + + query := &org.SearchOrgUsersQuery{ + OrgID: signedInUser.GetOrgID(), + Limit: int(req.Limit), + Page: int(req.Page), + SortOpts: legacySortOptions, + + User: signedInUser, } var title, login, email string @@ -76,19 +99,15 @@ func (c *UserLegacySearchClient) Search(ctx context.Context, req *resourcepb.Res c.log.Warn("only single value fields are supported for legacy search, using first value", "field", field.Key, "values", vals) } switch field.Key { - case res.SEARCH_FIELD_TITLE: + case resource.SEARCH_FIELD_TITLE: title = vals[0] - case "fields.login": + case fieldLogin: login = vals[0] - case "fields.email": + case fieldEmail: email = vals[0] } } - if title == "" && login == "" && email == "" { - return nil, fmt.Errorf("at least one of title, login, or email must be provided for the query") - } - // The user store's Search method combines these into an OR. // For legacy search we can only supply one. if title != "" { @@ -99,20 +118,35 @@ func (c *UserLegacySearchClient) Search(ctx context.Context, req *resourcepb.Res query.Query = email } - columns := getColumns(req.Fields) + // Unified search `query` has wildcards, but legacy search does not support them. + // We have to remove them here to make legacy search work as expected with SQL LIKE queries. + if req.Query != "" { + query.Query = wildcardsMatcher.ReplaceAllString(req.Query, "") + } + + fields := req.Fields + if len(fields) == 0 { + fields = []string{resource.SEARCH_FIELD_TITLE, fieldEmail, fieldLogin, fieldLastSeenAt, fieldRole} + } + + columns := getColumns(fields) list := &resourcepb.ResourceSearchResponse{ Results: &resourcepb.ResourceTable{ Columns: columns, }, } - res, err := c.userService.Search(ctx, query) + res, err := c.orgService.SearchOrgUsers(ctx, query) if err != nil { return nil, err } - for _, u := range res.Users { - cells := createBaseCells(u, req.Fields) + for _, u := range res.OrgUsers { + if c.isHiddenUser(u.Login, signedInUser) { + continue + } + + cells := createCells(u, req.Fields) list.Results.Rows = append(list.Results.Rows, &resourcepb.ResourceTableRow{ Key: getResourceKey(u, req.Options.Key.Namespace), Cells: cells, @@ -123,7 +157,19 @@ func (c *UserLegacySearchClient) Search(ctx context.Context, req *resourcepb.Res return list, nil } -func getResourceKey(item *user.UserSearchHitDTO, namespace string) *resourcepb.ResourceKey { +func (c *UserLegacySearchClient) isHiddenUser(login string, signedInUser identity.Requester) bool { + if login == "" || signedInUser.GetIsGrafanaAdmin() || login == signedInUser.GetUsername() { + return false + } + + if _, hidden := c.cfg.HiddenUsers[login]; hidden { + return true + } + + return false +} + +func getResourceKey(item *org.OrgUserDTO, namespace string) *resourcepb.ResourceKey { return &resourcepb.ResourceKey{ Namespace: namespace, Group: UserResourceGroup, @@ -133,42 +179,74 @@ func getResourceKey(item *user.UserSearchHitDTO, namespace string) *resourcepb.R } func getColumns(fields []string) []*resourcepb.ResourceTableColumnDefinition { - columns := defaultColumns() + cols := make([]*resourcepb.ResourceTableColumnDefinition, 0, len(fields)) + standardSearchFields := resource.StandardSearchFields() for _, field := range fields { switch field { - case "email": - columns = append(columns, builders.UserTableColumnDefinitions[builders.USER_EMAIL]) - case "login": - columns = append(columns, builders.UserTableColumnDefinitions[builders.USER_LOGIN]) + case resource.SEARCH_FIELD_TITLE: + cols = append(cols, standardSearchFields.Field(resource.SEARCH_FIELD_TITLE)) + case fieldLastSeenAt: + cols = append(cols, builders.UserTableColumnDefinitions[builders.USER_LAST_SEEN_AT]) + case fieldRole: + cols = append(cols, builders.UserTableColumnDefinitions[builders.USER_ROLE]) + case fieldEmail: + cols = append(cols, builders.UserTableColumnDefinitions[builders.USER_EMAIL]) + case fieldLogin: + cols = append(cols, builders.UserTableColumnDefinitions[builders.USER_LOGIN]) } } - return columns + return cols } -func createBaseCells(u *user.UserSearchHitDTO, fields []string) [][]byte { - cells := createDefaultCells(u) +func createCells(u *org.OrgUserDTO, fields []string) [][]byte { + cells := make([][]byte, 0, len(fields)) for _, field := range fields { switch field { - case "email": + case resource.SEARCH_FIELD_TITLE: + cells = append(cells, []byte(u.Name)) + case fieldEmail: cells = append(cells, []byte(u.Email)) - case "login": + case fieldLogin: cells = append(cells, []byte(u.Login)) + case fieldLastSeenAt: + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(u.LastSeenAt.Unix())) + cells = append(cells, b) + case fieldRole: + cells = append(cells, []byte(u.Role)) } } return cells } -func createDefaultCells(u *user.UserSearchHitDTO) [][]byte { - return [][]byte{ - []byte(u.UID), - []byte(u.Name), - } -} +func convertToSortOptions(sortBy []*resourcepb.ResourceSearchRequest_Sort) []model.SortOption { + opts := []model.SortOption{} + for _, s := range sortBy { + field := s.Field + // Handle mapping if necessary + switch field { + case fieldLastSeenAt: + field = "lastSeenAtAge" + case resource.SEARCH_FIELD_TITLE: + field = "name" + case fieldLogin: + field = "login" + case fieldEmail: + field = "email" + } -func defaultColumns() []*resourcepb.ResourceTableColumnDefinition { - searchFields := res.StandardSearchFields() - return []*resourcepb.ResourceTableColumnDefinition{ - searchFields.Field(res.SEARCH_FIELD_NAME), - searchFields.Field(res.SEARCH_FIELD_TITLE), + suffix := "asc" + if s.Desc { + suffix = "desc" + } + key := fmt.Sprintf("%s-%s", field, suffix) + + if opt, ok := sortopts.SortOptionsByQueryParam[key]; ok { + opts = append(opts, opt) + } } + sort.Slice(opts, func(i, j int) bool { + return opts[i].Index < opts[j].Index || (opts[i].Index == opts[j].Index && opts[i].Name < opts[j].Name) + }) + return opts } diff --git a/pkg/registry/apis/iam/user/legacy_search_fake.go b/pkg/registry/apis/iam/user/legacy_search_fake.go index b68e944c84c..e2effd7947e 100644 --- a/pkg/registry/apis/iam/user/legacy_search_fake.go +++ b/pkg/registry/apis/iam/user/legacy_search_fake.go @@ -5,7 +5,7 @@ import ( "google.golang.org/grpc" - "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" ) @@ -13,7 +13,7 @@ import ( type FakeUserLegacySearchClient struct { resourcepb.ResourceIndexClient SearchFunc func(ctx context.Context, req *resourcepb.ResourceSearchRequest, opts ...grpc.CallOption) (*resourcepb.ResourceSearchResponse, error) - Users []*user.UserSearchHitDTO + Users []*org.OrgUserDTO } // Search calls the underlying SearchFunc or simulates a search over the Users slice. @@ -23,7 +23,7 @@ func (c *FakeUserLegacySearchClient) Search(ctx context.Context, req *resourcepb } // Basic filtering for testing purposes - var filteredUsers []*user.UserSearchHitDTO + var filteredUsers []*org.OrgUserDTO var queryValue string for _, field := range req.Options.Fields { @@ -43,7 +43,7 @@ func (c *FakeUserLegacySearchClient) Search(ctx context.Context, req *resourcepb for _, u := range filteredUsers { rows = append(rows, &resourcepb.ResourceTableRow{ Key: getResourceKey(u, req.Options.Key.Namespace), - Cells: createBaseCells(u, req.Fields), + Cells: createCells(u, req.Fields), }) } diff --git a/pkg/registry/apis/iam/user/legacy_search_test.go b/pkg/registry/apis/iam/user/legacy_search_test.go index 6bd49185786..e3dc873fe3c 100644 --- a/pkg/registry/apis/iam/user/legacy_search_test.go +++ b/pkg/registry/apis/iam/user/legacy_search_test.go @@ -9,29 +9,15 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/org" + "github.com/grafana/grafana/pkg/services/org/orgtest" "github.com/grafana/grafana/pkg/services/user" - "github.com/grafana/grafana/pkg/services/user/usertest" + "github.com/grafana/grafana/pkg/setting" res "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" ) func TestUserLegacySearchClient_Search(t *testing.T) { - t.Run("should return error if no query fields are provided", func(t *testing.T) { - mockUserService := usertest.NewMockService(t) - client := NewUserLegacySearchClient(mockUserService, tracing.NewNoopTracerService()) - ctx := identity.WithRequester(context.Background(), &user.SignedInUser{OrgID: 1, UserID: 1}) - req := &resourcepb.ResourceSearchRequest{ - Options: &resourcepb.ListOptions{ - Key: &resourcepb.ResourceKey{Namespace: "default"}, - }, - } - - _, err := client.Search(ctx, req) - - require.Error(t, err) - require.Equal(t, "at least one of title, login, or email must be provided for the query", err.Error()) - }) - testCases := []struct { name string fieldKey string @@ -66,8 +52,8 @@ func TestUserLegacySearchClient_Search(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - mockUserService := usertest.NewMockService(t) - client := NewUserLegacySearchClient(mockUserService, tracing.NewNoopTracerService()) + mockOrgService := orgtest.NewMockService(t) + client := NewUserLegacySearchClient(mockOrgService, tracing.NewNoopTracerService(), &setting.Cfg{}) ctx := identity.WithRequester(context.Background(), &user.SignedInUser{OrgID: 1, UserID: 1}) req := &resourcepb.ResourceSearchRequest{ Limit: 10, @@ -81,14 +67,14 @@ func TestUserLegacySearchClient_Search(t *testing.T) { Fields: []string{"email", "login"}, } - mockUsers := []*user.UserSearchHitDTO{ - {ID: 1, UID: "uid1", Name: "Test User 1", Email: "test1@example.com", Login: "testlogin1"}, + mockUsers := []*org.OrgUserDTO{ + {UID: "uid1", Name: "Test User 1", Email: "test1@example.com", Login: "testlogin1"}, } - mockUserService.On("Search", mock.Anything, mock.MatchedBy(func(q *user.SearchUsersQuery) bool { + mockOrgService.On("SearchOrgUsers", mock.Anything, mock.MatchedBy(func(q *org.SearchOrgUsersQuery) bool { return q.Query == tc.expectedQuery && q.Limit == 10 && q.Page == 1 - })).Return(&user.SearchUserQueryResult{ - Users: mockUsers, + })).Return(&org.SearchOrgUsersQueryResult{ + OrgUsers: mockUsers, TotalCount: 1, }, nil) @@ -113,7 +99,7 @@ func TestUserLegacySearchClient_Search(t *testing.T) { require.Equal(t, UserResource, row.Key.Resource) require.Equal(t, u.UID, row.Key.Name) - expectedCells := createBaseCells(&user.UserSearchHitDTO{ + expectedCells := createCells(&org.OrgUserDTO{ UID: u.UID, Name: u.Name, Email: u.Email, @@ -125,8 +111,8 @@ func TestUserLegacySearchClient_Search(t *testing.T) { } t.Run("title should have precedence over login and email", func(t *testing.T) { - mockUserService := usertest.NewMockService(t) - client := NewUserLegacySearchClient(mockUserService, tracing.NewNoopTracerService()) + mockOrgService := orgtest.NewMockService(t) + client := NewUserLegacySearchClient(mockOrgService, tracing.NewNoopTracerService(), &setting.Cfg{}) ctx := identity.WithRequester(context.Background(), &user.SignedInUser{OrgID: 1, UserID: 1}) req := &resourcepb.ResourceSearchRequest{ Options: &resourcepb.ListOptions{ @@ -139,9 +125,9 @@ func TestUserLegacySearchClient_Search(t *testing.T) { }, } - mockUserService.On("Search", mock.Anything, mock.MatchedBy(func(q *user.SearchUsersQuery) bool { + mockOrgService.On("SearchOrgUsers", mock.Anything, mock.MatchedBy(func(q *org.SearchOrgUsersQuery) bool { return q.Query == "title" - })).Return(&user.SearchUserQueryResult{Users: []*user.UserSearchHitDTO{}, TotalCount: 0}, nil) + })).Return(&org.SearchOrgUsersQueryResult{OrgUsers: []*org.OrgUserDTO{}, TotalCount: 0}, nil) _, err := client.Search(ctx, req) require.NoError(t, err) diff --git a/pkg/registry/apis/iam/user/search.go b/pkg/registry/apis/iam/user/search.go new file mode 100644 index 00000000000..d2de838105a --- /dev/null +++ b/pkg/registry/apis/iam/user/search.go @@ -0,0 +1,399 @@ +package user + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "log/slog" + "net/http" + "net/url" + "regexp" + "slices" + "strconv" + "strings" + "time" + + "go.opentelemetry.io/otel/trace" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/spec3" + "k8s.io/kube-openapi/pkg/validation/spec" + + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/apiserver/builder" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/grafana/grafana/pkg/storage/unified/resourcepb" + "github.com/grafana/grafana/pkg/storage/unified/search/builders" + "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/util/errhttp" +) + +const maxLimit = 100 + +type SearchHandler struct { + log *slog.Logger + client resourcepb.ResourceIndexClient + tracer trace.Tracer + features featuremgmt.FeatureToggles + cfg *setting.Cfg +} + +func NewSearchHandler(tracer trace.Tracer, searchClient resourcepb.ResourceIndexClient, features featuremgmt.FeatureToggles, cfg *setting.Cfg) *SearchHandler { + return &SearchHandler{ + client: searchClient, + log: slog.Default().With("logger", "grafana-apiserver.user.search"), + tracer: tracer, + features: features, + cfg: cfg, + } +} + +func (s *SearchHandler) GetAPIRoutes(defs map[string]common.OpenAPIDefinition) *builder.APIRoutes { + searchResults := defs["github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GetSearchUsers"].Schema + return &builder.APIRoutes{ + Namespace: []builder.APIRouteHandler{ + { + Path: "searchUsers", + Spec: &spec3.PathProps{ + Get: &spec3.Operation{ + OperationProps: spec3.OperationProps{ + Description: "User search", + Tags: []string{"Search"}, + OperationId: "getSearchUsers", + Parameters: []*spec3.Parameter{ + { + ParameterProps: spec3.ParameterProps{ + Name: "namespace", + In: "path", + Required: true, + Example: "default", + Description: "workspace", + Schema: spec.StringProperty(), + }, + }, + { + ParameterProps: spec3.ParameterProps{ + Name: "query", + In: "query", + Required: false, + Schema: spec.StringProperty(), + }, + }, + { + ParameterProps: spec3.ParameterProps{ + Name: "limit", + In: "query", + Description: "number of results to return", + Example: 30, + Required: false, + Schema: spec.Int64Property(), + }, + }, + { + ParameterProps: spec3.ParameterProps{ + Name: "page", + In: "query", + Description: "page number (starting from 1)", + Example: 1, + Required: false, + Schema: spec.Int64Property(), + }, + }, + { + ParameterProps: spec3.ParameterProps{ + Name: "offset", + In: "query", + Description: "number of results to skip", + Example: 0, + Required: false, + Schema: spec.Int64Property(), + }, + }, + { + ParameterProps: spec3.ParameterProps{ + Name: "sort", + In: "query", + Description: "sortable field", + Example: "", + Examples: map[string]*spec3.Example{ + "": { + ExampleProps: spec3.ExampleProps{ + Summary: "default sorting", + Value: "", + }, + }, + "title": { + ExampleProps: spec3.ExampleProps{ + Summary: "title ascending", + Value: "title", + }, + }, + "-title": { + ExampleProps: spec3.ExampleProps{ + Summary: "title descending", + Value: "-title", + }, + }, + "lastSeenAt": { + ExampleProps: spec3.ExampleProps{ + Summary: "last seen at ascending", + Value: "lastSeenAt", + }, + }, + "-lastSeenAt": { + ExampleProps: spec3.ExampleProps{ + Summary: "last seen at descending", + Value: "-lastSeenAt", + }, + }, + "email": { + ExampleProps: spec3.ExampleProps{ + Summary: "email ascending", + Value: "email", + }, + }, + "-email": { + ExampleProps: spec3.ExampleProps{ + Summary: "email descending", + Value: "-email", + }, + }, + "login": { + ExampleProps: spec3.ExampleProps{ + Summary: "login ascending", + Value: "login", + }, + }, + "-login": { + ExampleProps: spec3.ExampleProps{ + Summary: "login descending", + Value: "-login", + }, + }, + }, + Required: false, + Schema: spec.StringProperty(), + }, + }, + }, + Responses: &spec3.Responses{ + ResponsesProps: spec3.ResponsesProps{ + Default: &spec3.Response{ + ResponseProps: spec3.ResponseProps{ + Description: "Default OK response", + Content: map[string]*spec3.MediaType{ + "application/json": { + MediaTypeProps: spec3.MediaTypeProps{ + Schema: &searchResults, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + Handler: s.DoSearch, + }, + }, + } +} + +func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) { + ctx, span := s.tracer.Start(r.Context(), "user.search") + defer span.End() + + queryParams, err := url.ParseQuery(r.URL.RawQuery) + if err != nil { + errhttp.Write(ctx, err, w) + return + } + + requester, err := identity.GetRequester(ctx) + if err != nil { + errhttp.Write(ctx, fmt.Errorf("no identity found for request: %w", err), w) + return + } + + limit := 30 + offset := 0 + page := 1 + if queryParams.Has("limit") { + limit, _ = strconv.Atoi(queryParams.Get("limit")) + } + if queryParams.Has("offset") { + offset, _ = strconv.Atoi(queryParams.Get("offset")) + if offset > 0 && limit > 0 { + page = (offset / limit) + 1 + } + } else if queryParams.Has("page") { + page, _ = strconv.Atoi(queryParams.Get("page")) + offset = (page - 1) * limit + } + + // Escape characters that are used by bleve wildcard search to be literal strings. + rawQuery := escapeBleveQuery(queryParams.Get("query")) + + searchQuery := fmt.Sprintf(`*%s*`, rawQuery) + + userGvr := iamv0.UserResourceInfo.GroupResource() + request := &resourcepb.ResourceSearchRequest{ + Options: &resourcepb.ListOptions{ + Key: &resourcepb.ResourceKey{ + Group: userGvr.Group, + Resource: userGvr.Resource, + Namespace: requester.GetNamespace(), + }, + }, + Query: searchQuery, + Fields: []string{resource.SEARCH_FIELD_TITLE, fieldEmail, fieldLogin, fieldLastSeenAt, fieldRole}, + Limit: int64(limit), + Page: int64(page), + Offset: int64(offset), + } + + if !requester.GetIsGrafanaAdmin() { + // FIXME: Use the new config service instead of the legacy one + hiddenUsers := []string{} + for user := range s.cfg.HiddenUsers { + if user != requester.GetUsername() { + hiddenUsers = append(hiddenUsers, user) + } + } + if len(hiddenUsers) > 0 { + request.Options.Fields = append(request.Options.Fields, &resourcepb.Requirement{ + Key: fieldLogin, + Operator: string(selection.NotIn), + Values: hiddenUsers, + }) + } + } + + if queryParams.Has("sort") { + for _, sort := range queryParams["sort"] { + currField := sort + desc := false + if strings.HasPrefix(sort, "-") { + currField = sort[1:] + desc = true + } + if slices.Contains(builders.UserSortableExtraFields, currField) { + sort = resource.SEARCH_FIELD_PREFIX + currField + } else { + sort = currField + } + s := &resourcepb.ResourceSearchRequest_Sort{ + Field: sort, + Desc: desc, + } + request.SortBy = append(request.SortBy, s) + } + } + + resp, err := s.client.Search(ctx, request) + if err != nil { + errhttp.Write(ctx, err, w) + return + } + + result, err := ParseResults(resp) + if err != nil { + errhttp.Write(ctx, err, w) + return + } + s.write(w, result) +} + +func (s *SearchHandler) write(w http.ResponseWriter, obj any) { + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(obj); err != nil { + s.log.Error("failed to encode JSON response", "error", err) + } +} + +func ParseResults(result *resourcepb.ResourceSearchResponse) (*iamv0.GetSearchUsers, error) { + if result == nil { + return iamv0.NewGetSearchUsers(), nil + } else if result.Error != nil { + return iamv0.NewGetSearchUsers(), fmt.Errorf("%d error searching: %s: %s", result.Error.Code, result.Error.Message, result.Error.Details) + } else if result.Results == nil { + return iamv0.NewGetSearchUsers(), nil + } + + titleIDX := -1 + emailIDX := -1 + loginIDX := -1 + lastSeenAtIDX := -1 + roleIDX := -1 + + for i, v := range result.Results.Columns { + switch v.Name { + case resource.SEARCH_FIELD_TITLE: + titleIDX = i + case builders.USER_EMAIL: + emailIDX = i + case builders.USER_LOGIN: + loginIDX = i + case builders.USER_LAST_SEEN_AT: + lastSeenAtIDX = i + case builders.USER_ROLE: + roleIDX = i + } + } + + sr := iamv0.NewGetSearchUsers() + sr.TotalHits = result.TotalHits + sr.QueryCost = result.QueryCost + sr.MaxScore = result.MaxScore + sr.Hits = make([]iamv0.UserHit, 0, len(result.Results.Rows)) + + for _, row := range result.Results.Rows { + if len(row.Cells) != len(result.Results.Columns) { + return iamv0.NewGetSearchUsers(), fmt.Errorf("error parsing user search response: mismatch number of columns and cells") + } + + var login string + if loginIDX >= 0 && row.Cells[loginIDX] != nil { + login = string(row.Cells[loginIDX]) + } + + hit := iamv0.UserHit{ + Name: row.Key.Name, + Login: login, + } + + if titleIDX >= 0 && row.Cells[titleIDX] != nil { + hit.Title = string(row.Cells[titleIDX]) + } + + if emailIDX >= 0 && row.Cells[emailIDX] != nil { + hit.Email = string(row.Cells[emailIDX]) + } + + if roleIDX >= 0 && row.Cells[roleIDX] != nil { + hit.Role = string(row.Cells[roleIDX]) + } + + if lastSeenAtIDX >= 0 && row.Cells[lastSeenAtIDX] != nil { + if len(row.Cells[lastSeenAtIDX]) == 8 { + hit.LastSeenAt = int64(binary.BigEndian.Uint64(row.Cells[lastSeenAtIDX])) + hit.LastSeenAtAge = util.GetAgeString(time.Unix(hit.LastSeenAt, 0)) + } + } + + sr.Hits = append(sr.Hits, hit) + } + + return sr, nil +} + +var bleveEscapeRegex = regexp.MustCompile(`([\\*?])`) + +func escapeBleveQuery(query string) string { + return bleveEscapeRegex.ReplaceAllString(query, `\$1`) +} diff --git a/pkg/registry/apis/iam/user/search_test.go b/pkg/registry/apis/iam/user/search_test.go new file mode 100644 index 00000000000..588ab37da39 --- /dev/null +++ b/pkg/registry/apis/iam/user/search_test.go @@ -0,0 +1,169 @@ +package user + +import ( + "context" + "net/http/httptest" + "testing" + + "google.golang.org/grpc" + + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apiserver/rest" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/featuremgmt" + legacyuser "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" + "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/grafana/grafana/pkg/storage/unified/resourcepb" +) + +func TestSearchFallback(t *testing.T) { + tests := []struct { + name string + mode rest.DualWriterMode + expectUnified bool + }{ + {name: "should hit legacy search handler on mode 0", mode: rest.Mode0, expectUnified: false}, + {name: "should hit legacy search handler on mode 1", mode: rest.Mode1, expectUnified: false}, + {name: "should hit legacy search handler on mode 2", mode: rest.Mode2, expectUnified: false}, + {name: "should hit unified storage search handler on mode 3", mode: rest.Mode3, expectUnified: true}, + {name: "should hit unified storage search handler on mode 4", mode: rest.Mode4, expectUnified: true}, + {name: "should hit unified storage search handler on mode 5", mode: rest.Mode5, expectUnified: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient := &MockClient{} + mockLegacyClient := &MockClient{} + + cfg := &setting.Cfg{ + UnifiedStorage: map[string]setting.UnifiedStorageConfig{ + "users.iam.grafana.app": {DualWriterMode: tt.mode}, + }, + } + dual := dualwrite.ProvideStaticServiceForTests(cfg) + + searchClient := resource.NewSearchClient(dualwrite.NewSearchAdapter(dual), iamv0.UserResourceInfo.GroupResource(), mockClient, mockLegacyClient, featuremgmt.WithFeatures()) + searchHandler := NewSearchHandler(tracing.NewNoopTracerService(), searchClient, featuremgmt.WithFeatures(), cfg) + + rr := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/searchUsers", nil) + req.Header.Add("content-type", "application/json") + req = req.WithContext(identity.WithRequester(req.Context(), &legacyuser.SignedInUser{Namespace: "test"})) + + searchHandler.DoSearch(rr, req) + + if tt.expectUnified { + if mockClient.LastSearchRequest == nil { + t.Fatalf("expected Unified Search to be called, but it was not") + } + } else { + if mockLegacyClient.LastSearchRequest == nil { + t.Fatalf("expected Legacy Search to be called, but it was not") + } + } + }) + } +} + +// MockClient implements the ResourceIndexClient interface for testing +type MockClient struct { + resourcepb.ResourceIndexClient + resource.ResourceIndex + + LastSearchRequest *resourcepb.ResourceSearchRequest + + MockResponses []*resourcepb.ResourceSearchResponse + MockCalls []*resourcepb.ResourceSearchRequest + CallCount int +} + +func (m *MockClient) Search(ctx context.Context, in *resourcepb.ResourceSearchRequest, opts ...grpc.CallOption) (*resourcepb.ResourceSearchResponse, error) { + m.LastSearchRequest = in + m.MockCalls = append(m.MockCalls, in) + + var response *resourcepb.ResourceSearchResponse + if m.CallCount < len(m.MockResponses) { + response = m.MockResponses[m.CallCount] + } + + m.CallCount = m.CallCount + 1 + + if response == nil { + response = &resourcepb.ResourceSearchResponse{} + } + + return response, nil +} +func (m *MockClient) GetStats(ctx context.Context, in *resourcepb.ResourceStatsRequest, opts ...grpc.CallOption) (*resourcepb.ResourceStatsResponse, error) { + return nil, nil +} +func (m *MockClient) CountManagedObjects(ctx context.Context, in *resourcepb.CountManagedObjectsRequest, opts ...grpc.CallOption) (*resourcepb.CountManagedObjectsResponse, error) { + return nil, nil +} +func (m *MockClient) Watch(ctx context.Context, in *resourcepb.WatchRequest, opts ...grpc.CallOption) (resourcepb.ResourceStore_WatchClient, error) { + return nil, nil +} +func (m *MockClient) Delete(ctx context.Context, in *resourcepb.DeleteRequest, opts ...grpc.CallOption) (*resourcepb.DeleteResponse, error) { + return nil, nil +} +func (m *MockClient) Create(ctx context.Context, in *resourcepb.CreateRequest, opts ...grpc.CallOption) (*resourcepb.CreateResponse, error) { + return nil, nil +} +func (m *MockClient) Update(ctx context.Context, in *resourcepb.UpdateRequest, opts ...grpc.CallOption) (*resourcepb.UpdateResponse, error) { + return nil, nil +} +func (m *MockClient) Read(ctx context.Context, in *resourcepb.ReadRequest, opts ...grpc.CallOption) (*resourcepb.ReadResponse, error) { + return nil, nil +} +func (m *MockClient) GetBlob(ctx context.Context, in *resourcepb.GetBlobRequest, opts ...grpc.CallOption) (*resourcepb.GetBlobResponse, error) { + return nil, nil +} +func (m *MockClient) PutBlob(ctx context.Context, in *resourcepb.PutBlobRequest, opts ...grpc.CallOption) (*resourcepb.PutBlobResponse, error) { + return nil, nil +} +func (m *MockClient) List(ctx context.Context, in *resourcepb.ListRequest, opts ...grpc.CallOption) (*resourcepb.ListResponse, error) { + return nil, nil +} +func (m *MockClient) ListManagedObjects(ctx context.Context, in *resourcepb.ListManagedObjectsRequest, opts ...grpc.CallOption) (*resourcepb.ListManagedObjectsResponse, error) { + return nil, nil +} +func (m *MockClient) IsHealthy(ctx context.Context, in *resourcepb.HealthCheckRequest, opts ...grpc.CallOption) (*resourcepb.HealthCheckResponse, error) { + return nil, nil +} +func (m *MockClient) BulkProcess(ctx context.Context, opts ...grpc.CallOption) (resourcepb.BulkStore_BulkProcessClient, error) { + return nil, nil +} +func (m *MockClient) UpdateIndex(ctx context.Context, reason string) error { + return nil +} + +func (m *MockClient) GetQuotaUsage(ctx context.Context, req *resourcepb.QuotaUsageRequest, opts ...grpc.CallOption) (*resourcepb.QuotaUsageResponse, error) { + return nil, nil +} + +func TestEscapeBleveQuery(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {input: "normal", expected: "normal"}, + {input: "*", expected: "\\*"}, + {input: "?", expected: "\\?"}, + {input: "\\", expected: "\\\\"}, + {input: "\\*", expected: "\\\\\\*"}, + {input: "*\\?", expected: "\\*\\\\\\?"}, + {input: "foo*bar", expected: "foo\\*bar"}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + got := escapeBleveQuery(tt.input) + if got != tt.expected { + t.Errorf("escapeBleveQuery(%q) = %q, want %q", tt.input, got, tt.expected) + } + }) + } +} diff --git a/pkg/registry/apis/iam/user/store.go b/pkg/registry/apis/iam/user/store.go index 97daee3af18..53a146412ea 100644 --- a/pkg/registry/apis/iam/user/store.go +++ b/pkg/registry/apis/iam/user/store.go @@ -3,7 +3,6 @@ package user import ( "context" "fmt" - "time" "go.opentelemetry.io/otel/trace" "k8s.io/apimachinery/pkg/apis/meta/internalversion" @@ -35,7 +34,7 @@ var ( _ rest.TableConvertor = (*LegacyStore)(nil) ) -var resource = iamv0alpha1.UserResourceInfo +var userResource = iamv0alpha1.UserResourceInfo func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient, enableAuthnMutation bool, tracer trace.Tracer) *LegacyStore { return &LegacyStore{store, ac, enableAuthnMutation, tracer} @@ -54,7 +53,7 @@ func (s *LegacyStore) Update(ctx context.Context, name string, objInfo rest.Upda defer span.End() if !s.enableAuthnMutation { - return nil, false, apierrors.NewMethodNotSupported(resource.GroupResource(), "update") + return nil, false, apierrors.NewMethodNotSupported(userResource.GroupResource(), "update") } ns, err := request.NamespaceInfoFrom(ctx, true) @@ -105,7 +104,7 @@ func (s *LegacyStore) Update(ctx context.Context, name string, objInfo rest.Upda // DeleteCollection implements rest.CollectionDeleter. func (s *LegacyStore) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) { - return nil, apierrors.NewMethodNotSupported(resource.GroupResource(), "deletecollection") + return nil, apierrors.NewMethodNotSupported(userResource.GroupResource(), "deletecollection") } // Delete implements rest.GracefulDeleter. @@ -114,7 +113,7 @@ func (s *LegacyStore) Delete(ctx context.Context, name string, deleteValidation defer span.End() if !s.enableAuthnMutation { - return nil, false, apierrors.NewMethodNotSupported(resource.GroupResource(), "delete") + return nil, false, apierrors.NewMethodNotSupported(userResource.GroupResource(), "delete") } ns, err := request.NamespaceInfoFrom(ctx, true) @@ -131,7 +130,7 @@ func (s *LegacyStore) Delete(ctx context.Context, name string, deleteValidation return nil, false, err } if found == nil || len(found.Items) < 1 { - return nil, false, resource.NewNotFound(name) + return nil, false, userResource.NewNotFound(name) } userToDelete := &found.Items[0] @@ -157,7 +156,7 @@ func (s *LegacyStore) Delete(ctx context.Context, name string, deleteValidation } func (s *LegacyStore) New() runtime.Object { - return resource.NewFunc() + return userResource.NewFunc() } func (s *LegacyStore) Destroy() {} @@ -167,15 +166,15 @@ func (s *LegacyStore) NamespaceScoped() bool { } func (s *LegacyStore) GetSingularName() string { - return resource.GetSingularName() + return userResource.GetSingularName() } func (s *LegacyStore) NewList() runtime.Object { - return resource.NewListFunc() + return userResource.NewListFunc() } func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return resource.TableConverter().ConvertToTable(ctx, object, tableOptions) + return userResource.TableConverter().ConvertToTable(ctx, object, tableOptions) } func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { @@ -183,7 +182,7 @@ func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOpt defer span.End() res, err := common.List( - ctx, resource, s.ac, common.PaginationFromListOptions(options), + ctx, userResource, s.ac, common.PaginationFromListOptions(options), func(ctx context.Context, ns claims.NamespaceInfo, p common.Pagination) (*common.ListResponse[iamv0alpha1.User], error) { found, err := s.store.ListUsers(ctx, ns, legacy.ListUserQuery{ Pagination: p, @@ -231,10 +230,10 @@ func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetO Pagination: common.Pagination{Limit: 1}, }) if found == nil || err != nil { - return nil, resource.NewNotFound(name) + return nil, userResource.NewNotFound(name) } if len(found.Items) < 1 { - return nil, resource.NewNotFound(name) + return nil, userResource.NewNotFound(name) } obj := toUserItem(&found.Items[0], ns.Value) @@ -247,7 +246,7 @@ func (s *LegacyStore) Create(ctx context.Context, obj runtime.Object, createVali defer span.End() if !s.enableAuthnMutation { - return nil, apierrors.NewMethodNotSupported(resource.GroupResource(), "create") + return nil, apierrors.NewMethodNotSupported(userResource.GroupResource(), "create") } ns, err := request.NamespaceInfoFrom(ctx, true) @@ -310,18 +309,12 @@ func toUserItem(u *common.UserWithRole, ns string) iamv0alpha1.User { Provisioned: u.IsProvisioned, Role: u.Role, }, + Status: iamv0alpha1.UserStatus{ + LastSeenAt: u.LastSeenAt.Unix(), + }, } obj, _ := utils.MetaAccessor(item) obj.SetUpdatedTimestamp(&u.Updated) - obj.SetAnnotation(AnnoKeyLastSeenAt, formatTime(&u.LastSeenAt)) obj.SetDeprecatedInternalID(u.ID) // nolint:staticcheck return *item } - -func formatTime(v *time.Time) string { - txt := "" - if v != nil && v.Unix() != 0 { - txt = v.UTC().Format(time.RFC3339) - } - return txt -} diff --git a/pkg/registry/apis/iam/user/validate.go b/pkg/registry/apis/iam/user/validate.go index fb4e0791689..1fa1d28a875 100644 --- a/pkg/registry/apis/iam/user/validate.go +++ b/pkg/registry/apis/iam/user/validate.go @@ -128,7 +128,7 @@ func validateEmail(ctx context.Context, searchClient resourcepb.ResourceIndexCli Operator: string(selection.Equals), Values: []string{email}, }, - }, []string{"name", "email", "login"}) + }, []string{"fields.email", "fields.login"}) resp, err := searchClient.Search(ctx, req) if err != nil { @@ -159,7 +159,7 @@ func validateLogin(ctx context.Context, searchClient resourcepb.ResourceIndexCli Operator: string(selection.Equals), Values: []string{login}, }, - }, []string{"name", "email", "login"}) + }, []string{"fields.email", "fields.login"}) resp, err := searchClient.Search(ctx, req) if err != nil { return err diff --git a/pkg/registry/apis/iam/user/validate_test.go b/pkg/registry/apis/iam/user/validate_test.go index fb558912ab0..17909f2ca7a 100644 --- a/pkg/registry/apis/iam/user/validate_test.go +++ b/pkg/registry/apis/iam/user/validate_test.go @@ -9,7 +9,7 @@ import ( "github.com/grafana/authlib/types" iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -178,7 +178,7 @@ func TestValidateOnCreate(t *testing.T) { IsGrafanaAdmin: false, }, searchClient: &FakeUserLegacySearchClient{ - Users: []*user.UserSearchHitDTO{ + Users: []*org.OrgUserDTO{ {Email: "existing@example"}, }, }, @@ -202,7 +202,7 @@ func TestValidateOnCreate(t *testing.T) { IsGrafanaAdmin: false, }, searchClient: &FakeUserLegacySearchClient{ - Users: []*user.UserSearchHitDTO{ + Users: []*org.OrgUserDTO{ {Login: "existinguser"}, }, }, @@ -490,7 +490,7 @@ func TestValidateOnUpdate(t *testing.T) { IsGrafanaAdmin: true, }, searchClient: &FakeUserLegacySearchClient{ - Users: []*user.UserSearchHitDTO{ + Users: []*org.OrgUserDTO{ {Email: "two@example"}, }, }, @@ -516,7 +516,7 @@ func TestValidateOnUpdate(t *testing.T) { IsGrafanaAdmin: true, }, searchClient: &FakeUserLegacySearchClient{ - Users: []*user.UserSearchHitDTO{ + Users: []*org.OrgUserDTO{ {Name: "other", UID: "uid456", Login: "two"}, }, }, @@ -536,7 +536,7 @@ func TestValidateOnUpdate(t *testing.T) { IsGrafanaAdmin: true, }, searchClient: &FakeUserLegacySearchClient{ - Users: []*user.UserSearchHitDTO{ + Users: []*org.OrgUserDTO{ {Login: "testuser", Email: "test@example"}, }, }, diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index f31f8c18948..566393e935f 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -879,7 +879,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api folderAPIBuilder := folders.RegisterAPIService(cfg, featureToggles, apiserverService, folderimplService, folderPermissionsService, accessControl, acimplService, accessClient, registerer, resourceClient, zanzanaClient) storageBackendImpl := noopstorage.ProvideStorageBackend() noopTeamGroupsREST := externalgroupmapping.ProvideNoopTeamGroupsREST() - identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, userService, teamService) + identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService) if err != nil { return nil, err } @@ -1537,7 +1537,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac folderAPIBuilder := folders.RegisterAPIService(cfg, featureToggles, apiserverService, folderimplService, folderPermissionsService, accessControl, acimplService, accessClient, registerer, resourceClient, zanzanaClient) storageBackendImpl := noopstorage.ProvideStorageBackend() noopTeamGroupsREST := externalgroupmapping.ProvideNoopTeamGroupsREST() - identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, userService, teamService) + identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService) if err != nil { return nil, err } diff --git a/pkg/storage/unified/search/builders/document_test.go b/pkg/storage/unified/search/builders/document_test.go index 6c8d02d6cb8..5fcae177696 100644 --- a/pkg/storage/unified/search/builders/document_test.go +++ b/pkg/storage/unified/search/builders/document_test.go @@ -53,8 +53,9 @@ func TestUserDocumentBuilder(t *testing.T) { Group: "iam.grafana.app", Resource: "users", }, []string{ - "user-with-login-and-email", - "user-with-login-only", + "with-login-and-email", + "with-login-only", + "with-last-seen-at-and-role", }) } diff --git a/pkg/storage/unified/search/builders/testdata/doc/user-with-last-seen-at-and-role-out.json b/pkg/storage/unified/search/builders/testdata/doc/user-with-last-seen-at-and-role-out.json new file mode 100644 index 00000000000..84e06fd5b90 --- /dev/null +++ b/pkg/storage/unified/search/builders/testdata/doc/user-with-last-seen-at-and-role-out.json @@ -0,0 +1,16 @@ +{ + "key": { + "namespace": "default", + "group": "iam.grafana.app", + "resource": "users", + "name": "with-last-seen-at-and-role" + }, + "name": "with-last-seen-at-and-role", + "rv": 1234, + "fields": { + "email": "user.three@test.com", + "lastSeenAt": 1698321600, + "login": "user.three", + "role": "Editor" + } +} diff --git a/pkg/storage/unified/search/builders/testdata/doc/user-with-last-seen-at-and-role.json b/pkg/storage/unified/search/builders/testdata/doc/user-with-last-seen-at-and-role.json new file mode 100644 index 00000000000..fdd964240a6 --- /dev/null +++ b/pkg/storage/unified/search/builders/testdata/doc/user-with-last-seen-at-and-role.json @@ -0,0 +1,14 @@ +{ + "metadata": { + "name": "with-last-seen-at-and-role", + "namespace": "default" + }, + "spec": { + "login": "user.three", + "email": "user.three@test.com", + "role": "Editor" + }, + "status": { + "lastSeenAt": 1698321600 + } +} diff --git a/pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-and-email-out.json b/pkg/storage/unified/search/builders/testdata/doc/user-with-login-and-email-out.json similarity index 55% rename from pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-and-email-out.json rename to pkg/storage/unified/search/builders/testdata/doc/user-with-login-and-email-out.json index b7f75247149..04fd081d011 100644 --- a/pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-and-email-out.json +++ b/pkg/storage/unified/search/builders/testdata/doc/user-with-login-and-email-out.json @@ -3,12 +3,14 @@ "namespace": "default", "group": "iam.grafana.app", "resource": "users", - "name": "user-with-login-and-email" + "name": "with-login-and-email" }, - "name": "user-with-login-and-email", + "name": "with-login-and-email", "rv": 1234, "fields": { "email": "user.one@test.com", - "login": "user.one" + "lastSeenAt": 0, + "login": "user.one", + "role": "Viewer" } } \ No newline at end of file diff --git a/pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-and-email.json b/pkg/storage/unified/search/builders/testdata/doc/user-with-login-and-email.json similarity index 79% rename from pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-and-email.json rename to pkg/storage/unified/search/builders/testdata/doc/user-with-login-and-email.json index a7eebbc754a..894bf6e977b 100644 --- a/pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-and-email.json +++ b/pkg/storage/unified/search/builders/testdata/doc/user-with-login-and-email.json @@ -1,6 +1,6 @@ { "metadata": { - "name": "user-with-login-and-email", + "name": "with-login-and-email", "namespace": "default" }, "spec": { diff --git a/pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-only-out.json b/pkg/storage/unified/search/builders/testdata/doc/user-with-login-only-out.json similarity index 51% rename from pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-only-out.json rename to pkg/storage/unified/search/builders/testdata/doc/user-with-login-only-out.json index 7672d4b2a09..3aeaeec020e 100644 --- a/pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-only-out.json +++ b/pkg/storage/unified/search/builders/testdata/doc/user-with-login-only-out.json @@ -3,11 +3,13 @@ "namespace": "default", "group": "iam.grafana.app", "resource": "users", - "name": "user-with-login-only" + "name": "with-login-only" }, - "name": "user-with-login-only", + "name": "with-login-only", "rv": 1234, "fields": { - "login": "user.two" + "lastSeenAt": 0, + "login": "user.two", + "role": "Viewer" } } \ No newline at end of file diff --git a/pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-only.json b/pkg/storage/unified/search/builders/testdata/doc/user-with-login-only.json similarity index 77% rename from pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-only.json rename to pkg/storage/unified/search/builders/testdata/doc/user-with-login-only.json index 6b3adf2dc1d..affbe8a9c5e 100644 --- a/pkg/storage/unified/search/builders/testdata/doc/user-user-with-login-only.json +++ b/pkg/storage/unified/search/builders/testdata/doc/user-with-login-only.json @@ -1,6 +1,6 @@ { "metadata": { - "name": "user-with-login-only", + "name": "with-login-only", "namespace": "default" }, "spec": { diff --git a/pkg/storage/unified/search/builders/user.go b/pkg/storage/unified/search/builders/user.go index b17cdb99b15..c18c9d33cdc 100644 --- a/pkg/storage/unified/search/builders/user.go +++ b/pkg/storage/unified/search/builders/user.go @@ -12,10 +12,20 @@ import ( ) const ( - USER_EMAIL = "email" - USER_LOGIN = "login" + USER_EMAIL = "email" + USER_LOGIN = "login" + USER_LAST_SEEN_AT = "lastSeenAt" + USER_ROLE = "role" ) +// UserSortableExtraFields are the additional fields that can be used for sorting user search results. +// Should not include standard fields like title. +var UserSortableExtraFields = []string{ + USER_EMAIL, + USER_LOGIN, + USER_LAST_SEEN_AT, +} + var UserTableColumnDefinitions = map[string]*resourcepb.ResourceTableColumnDefinition{ USER_EMAIL: { Name: USER_EMAIL, @@ -35,6 +45,22 @@ var UserTableColumnDefinitions = map[string]*resourcepb.ResourceTableColumnDefin Filterable: true, }, }, + USER_LAST_SEEN_AT: { + Name: USER_LAST_SEEN_AT, + Type: resourcepb.ResourceTableColumnDefinition_INT64, + Description: "The last seen timestamp of the user", + Properties: &resourcepb.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, + USER_ROLE: { + Name: USER_ROLE, + Type: resourcepb.ResourceTableColumnDefinition_STRING, + Description: "The role of the user", + Properties: &resourcepb.ResourceTableColumnDefinition_Properties{ + Filterable: true, + }, + }, } func GetUserBuilder() (resource.DocumentBuilderInfo, error) { @@ -75,6 +101,8 @@ func (u *userDocumentBuilder) BuildDocument(ctx context.Context, key *resourcepb if user.Spec.Login != "" { doc.Fields[USER_LOGIN] = user.Spec.Login } + doc.Fields[USER_LAST_SEEN_AT] = user.Status.LastSeenAt + doc.Fields[USER_ROLE] = user.Spec.Role return doc, nil } diff --git a/pkg/tests/apis/iam/user_search_integration_test.go b/pkg/tests/apis/iam/user_search_integration_test.go new file mode 100644 index 00000000000..1f689b701b0 --- /dev/null +++ b/pkg/tests/apis/iam/user_search_integration_test.go @@ -0,0 +1,568 @@ +package identity + +import ( + "context" + "fmt" + "net/url" + "sort" + "testing" + "time" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apiserver/rest" + "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/tests/apis" + "github.com/grafana/grafana/pkg/tests/testinfra" + "github.com/grafana/grafana/pkg/util/testutil" +) + +func TestIntegrationUserSearch(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5} + for _, mode := range modes { + t.Run(fmt.Sprintf("DualWriterMode %d", mode), func(t *testing.T) { + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + AppModeProduction: false, + DisableAnonymous: true, + APIServerStorageType: "unified", + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + "users.iam.grafana.app": { + DualWriterMode: mode, + }, + }, + EnableFeatureToggles: []string{ + featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, + featuremgmt.FlagKubernetesAuthnMutation, + }, + UnifiedStorageEnableSearch: true, + }) + + t.Cleanup(func() { + helper.Shutdown() + }) + + setupUsers(t, helper) + + t.Run("search by title", func(t *testing.T) { + res := searchUsers(t, helper, "Alice") + require.Len(t, res.Hits, 1) + require.Equal(t, "TestUser Alice", res.Hits[0].Title) + }) + + t.Run("search by login", func(t *testing.T) { + res := searchUsers(t, helper, "bob") + require.Len(t, res.Hits, 1) + require.Equal(t, "bob", res.Hits[0].Login) + }) + + t.Run("search by email", func(t *testing.T) { + res := searchUsers(t, helper, "charlie@example.com") + require.Len(t, res.Hits, 1) + require.Equal(t, "charlie@example.com", res.Hits[0].Email) + }) + }) + } +} + +func TestIntegrationUserSearch_WithSorting(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5} + for _, mode := range modes { + t.Run(fmt.Sprintf("DualWriterMode %d", mode), func(t *testing.T) { + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + AppModeProduction: false, + DisableAnonymous: true, + APIServerStorageType: "unified", + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + "users.iam.grafana.app": { + DualWriterMode: mode, + }, + }, + EnableFeatureToggles: []string{ + featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, + featuremgmt.FlagKubernetesAuthnMutation, + }, + UnifiedStorageEnableSearch: true, + }) + + t.Cleanup(func() { + helper.Shutdown() + }) + + setupUsers(t, helper) + + tests := []struct { + field string + extractor func(iamv0.UserHit) string + expected []string + }{ + { + field: "title", + extractor: func(h iamv0.UserHit) string { return h.Title }, + expected: []string{"TestUser Alice", "TestUser Bob", "TestUser Charlie", "TestUser Editor", "TestUser Viewer"}, + }, + { + field: "login", + extractor: func(h iamv0.UserHit) string { return h.Login }, + expected: []string{"alice", "bob", "charlie", "testuser-editor", "testuser-viewer"}, + }, + { + field: "email", + extractor: func(h iamv0.UserHit) string { return h.Email }, + expected: []string{"alice@example.com", "bob@example.com", "charlie@example.com", "testuser-editor@example.com", "testuser-viewer@example.com"}, + }, + } + + for _, tc := range tests { + t.Run("sort by "+tc.field, func(t *testing.T) { + // ASC + res := searchUsersWithSort(t, helper, "TestUser", tc.field) + require.GreaterOrEqual(t, len(res.Hits), 5) + verifyOrder(t, res.Hits, tc.expected, tc.extractor) + + // DESC + res = searchUsersWithSort(t, helper, "TestUser", "-"+tc.field) + require.GreaterOrEqual(t, len(res.Hits), 5) + + // Reverse expected + reversed := make([]string, len(tc.expected)) + copy(reversed, tc.expected) + sort.Sort(sort.Reverse(sort.StringSlice(reversed))) + verifyOrder(t, res.Hits, reversed, tc.extractor) + }) + } + + t.Run("sort by lastSeenAt", func(t *testing.T) { + if mode >= rest.Mode3 { + t.Skip("Skipping lastSeenAt sort test for Mode >= 3: API does not persist status.lastSeenAt") + } + // Populate lastSeenAt + // Alice: 30 minutes ago + // Bob: 1 minute ago + // Charlie: 2 hours ago + // Editor: 40 minutes ago + // Viewer: 1h 30 mins ago + now := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) + updateLastSeenAt(t, helper, "alice", now.Add(-30*time.Minute), mode) + updateLastSeenAt(t, helper, "bob", now.Add(-1*time.Minute), mode) + updateLastSeenAt(t, helper, "charlie", now.Add(-2*time.Hour), mode) + updateLastSeenAt(t, helper, "testuser-editor", now.Add(-40*time.Minute), mode) + updateLastSeenAt(t, helper, "testuser-viewer", now.Add(-90*time.Minute), mode) + + // lastSeenAt ASC means oldest date first to match legacy behavior + res := searchUsersWithSort(t, helper, "TestUser", "lastSeenAt") + require.GreaterOrEqual(t, len(res.Hits), 5) + verifyOrder(t, res.Hits, []string{"charlie", "testuser-viewer", "testuser-editor", "alice", "bob"}, func(h iamv0.UserHit) string { return h.Login }) + + res = searchUsersWithSort(t, helper, "TestUser", "-lastSeenAt") + require.GreaterOrEqual(t, len(res.Hits), 5) + verifyOrder(t, res.Hits, []string{"bob", "alice", "testuser-editor", "testuser-viewer", "charlie"}, func(h iamv0.UserHit) string { return h.Login }) + }) + }) + } +} + +func TestIntegrationUserSearch_SortCompareLegacy(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + modes := []rest.DualWriterMode{rest.Mode2} + for _, mode := range modes { + t.Run(fmt.Sprintf("DualWriterMode %d", mode), func(t *testing.T) { + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + AppModeProduction: false, + DisableAnonymous: true, + APIServerStorageType: "unified", + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + "users.iam.grafana.app": { + DualWriterMode: mode, + }, + }, + EnableFeatureToggles: []string{ + featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, + featuremgmt.FlagKubernetesAuthnMutation, + }, + UnifiedStorageEnableSearch: true, + }) + + t.Cleanup(func() { + helper.Shutdown() + }) + + setupUsers(t, helper) + + // Populate lastSeenAt for sorting comparison + now := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) + updateLastSeenAt(t, helper, "alice", now.Add(-30*time.Minute), mode) + updateLastSeenAt(t, helper, "bob", now.Add(-1*time.Minute), mode) + updateLastSeenAt(t, helper, "charlie", now.Add(-2*time.Hour), mode) + updateLastSeenAt(t, helper, "testuser-editor", now.Add(-40*time.Minute), mode) + updateLastSeenAt(t, helper, "testuser-viewer", now.Add(-90*time.Minute), mode) + + fields := []string{"login", "email", "name", "lastSeenAt"} + for _, field := range fields { + for _, order := range []string{"asc", "desc"} { + t.Run(fmt.Sprintf("compare %s %s", field, order), func(t *testing.T) { + // Legacy API uses "name" for Name/Title, "login" for Login, "email" for Email. + // "lastSeenAt" maps to "lastSeenAtAge" in legacy. + legacySort := field + if field == "lastSeenAt" { + legacySort = "lastSeenAtAge" + } + legacySort += "-" + order + + newSort := field + if order == "desc" { + newSort = "-" + field + } + + legacyRes := searchUsersLegacy(t, helper, "TestUser", legacySort) + newRes := searchUsersWithSort(t, helper, "TestUser", newSort) + + require.Equal(t, len(legacyRes), len(newRes.Hits)) + for i := range legacyRes { + require.Equal(t, legacyRes[i].Login, newRes.Hits[i].Login, "Mismatch at index %d for sort %s", i, newSort) + } + }) + } + } + }) + } +} + +func TestIntegrationUserSearch_Paging(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5} + for _, mode := range modes { + t.Run(fmt.Sprintf("DualWriterMode %d", mode), func(t *testing.T) { + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + AppModeProduction: false, + DisableAnonymous: true, + APIServerStorageType: "unified", + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + "users.iam.grafana.app": { + DualWriterMode: mode, + }, + }, + EnableFeatureToggles: []string{ + featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, + featuremgmt.FlagKubernetesAuthnMutation, + }, + UnifiedStorageEnableSearch: true, + }) + + t.Cleanup(func() { + helper.Shutdown() + }) + + setupUsers(t, helper) + + t.Run("paging with page and limit", func(t *testing.T) { + // There are 5 users matching "TestUser" + query := "TestUser" + + // Page 1, Limit 2 + res1 := searchUsersWithPaging(t, helper, query, 1, 2) + require.Equal(t, int64(5), res1.TotalHits) + require.Len(t, res1.Hits, 2) + + // Page 2, Limit 2 + res2 := searchUsersWithPaging(t, helper, query, 2, 2) + require.Equal(t, int64(5), res2.TotalHits) + require.Len(t, res2.Hits, 2) + + // Page 3, Limit 2 + res3 := searchUsersWithPaging(t, helper, query, 3, 2) + require.Equal(t, int64(5), res3.TotalHits) + require.Len(t, res3.Hits, 1) + + seen := make(map[string]bool) + for _, h := range res1.Hits { + seen[h.Login] = true + } + for _, h := range res2.Hits { + require.False(t, seen[h.Login], "User %s seen in page 1 and 2", h.Login) + seen[h.Login] = true + } + for _, h := range res3.Hits { + require.False(t, seen[h.Login], "User %s seen in previous pages", h.Login) + seen[h.Login] = true + } + require.Len(t, seen, 5) + }) + + t.Run("paging with offset and limit", func(t *testing.T) { + // There are 5 users matching "TestUser" + query := "TestUser" + + // Offset 0, Limit 2 (equivalent to Page 1) + res1 := searchUsersWithOffset(t, helper, query, 0, 2) + require.Equal(t, int64(5), res1.TotalHits) + require.Len(t, res1.Hits, 2) + + // Offset 2, Limit 2 (equivalent to Page 2) + res2 := searchUsersWithOffset(t, helper, query, 2, 2) + require.Equal(t, int64(5), res2.TotalHits) + require.Len(t, res2.Hits, 2) + + // Offset 4, Limit 2 (equivalent to Page 3) + res3 := searchUsersWithOffset(t, helper, query, 4, 2) + require.Equal(t, int64(5), res3.TotalHits) + require.Len(t, res3.Hits, 1) + + // Verify uniqueness + seen := make(map[string]bool) + for _, h := range res1.Hits { + seen[h.Login] = true + } + for _, h := range res2.Hits { + require.False(t, seen[h.Login], "User %s seen in offset 0 and 2", h.Login) + seen[h.Login] = true + } + for _, h := range res3.Hits { + require.False(t, seen[h.Login], "User %s seen in previous offsets", h.Login) + seen[h.Login] = true + } + require.Len(t, seen, 5) + }) + }) + } +} + +func setupUsers(t *testing.T, helper *apis.K8sTestHelper) { + ctx := context.Background() + userClient := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()), + GVR: gvrUsers, + }) + + users := []iamv0.User{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "testuser-editor", + }, + Spec: iamv0.UserSpec{ + Title: "TestUser Editor", + Login: "testuser-editor", + Email: "testuser-editor@example.com", + Role: "Editor", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "testuser-viewer", + }, + Spec: iamv0.UserSpec{ + Title: "TestUser Viewer", + Login: "testuser-viewer", + Email: "testuser-viewer@example.com", + Role: "Viewer", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "alice", + }, + Spec: iamv0.UserSpec{ + Title: "TestUser Alice", + Login: "alice", + Email: "alice@example.com", + Role: "Viewer", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "bob", + }, + Spec: iamv0.UserSpec{ + Title: "TestUser Bob", + Login: "bob", + Email: "bob@example.com", + Role: "Viewer", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "charlie", + }, + Spec: iamv0.UserSpec{ + Title: "TestUser Charlie", + Login: "charlie", + Email: "charlie@example.com", + Role: "Viewer", + }, + }, + } + + for _, u := range users { + uMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&u) + require.NoError(t, err) + _, err = userClient.Resource.Create(ctx, &unstructured.Unstructured{Object: uMap}, metav1.CreateOptions{}) + require.NoError(t, err) + } + + // Wait for indexing + time.Sleep(2 * time.Second) +} + +func searchUsers(t *testing.T, helper *apis.K8sTestHelper, query string) *iamv0.GetSearchUsers { + return searchUsersWithSort(t, helper, query, "") +} + +func searchUsersWithSort(t *testing.T, helper *apis.K8sTestHelper, query string, sort string) *iamv0.GetSearchUsers { + q := url.Values{} + q.Set("query", query) + if sort != "" { + q.Set("sort", sort) + } + q.Set("limit", "100") + + path := fmt.Sprintf("/apis/iam.grafana.app/v0alpha1/namespaces/default/searchUsers?%s", q.Encode()) + + res := &iamv0.GetSearchUsers{} + rsp := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.Admin, + Method: "GET", + Path: path, + }, res) + + require.Equal(t, 200, rsp.Response.StatusCode) + return res +} + +func searchUsersWithPaging(t *testing.T, helper *apis.K8sTestHelper, query string, page, limit int) *iamv0.GetSearchUsers { + q := url.Values{} + q.Set("query", query) + q.Set("page", fmt.Sprintf("%d", page)) + q.Set("limit", fmt.Sprintf("%d", limit)) + // Sort by login to ensure deterministic paging + q.Set("sort", "login") + + path := fmt.Sprintf("/apis/iam.grafana.app/v0alpha1/namespaces/default/searchUsers?%s", q.Encode()) + + res := &iamv0.GetSearchUsers{} + rsp := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.Admin, + Method: "GET", + Path: path, + }, res) + + require.Equal(t, 200, rsp.Response.StatusCode) + return res +} + +func searchUsersWithOffset(t *testing.T, helper *apis.K8sTestHelper, query string, offset, limit int) *iamv0.GetSearchUsers { + q := url.Values{} + q.Set("query", query) + q.Set("offset", fmt.Sprintf("%d", offset)) + q.Set("limit", fmt.Sprintf("%d", limit)) + // Sort by login to ensure deterministic paging + q.Set("sort", "login") + + path := fmt.Sprintf("/apis/iam.grafana.app/v0alpha1/namespaces/default/searchUsers?%s", q.Encode()) + + res := &iamv0.GetSearchUsers{} + rsp := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.Admin, + Method: "GET", + Path: path, + }, res) + + require.Equal(t, 200, rsp.Response.StatusCode) + return res +} + +type LegacyUserSearchHit struct { + UserId int64 `json:"userId"` + Name string `json:"name"` + Login string `json:"login"` + Email string `json:"email"` +} + +func searchUsersLegacy(t *testing.T, helper *apis.K8sTestHelper, query string, sort string) []LegacyUserSearchHit { + q := url.Values{} + q.Set("query", query) + + if sort != "" { + q.Set("sort", sort) + } + q.Set("perpage", "100") + q.Set("page", "1") + + path := fmt.Sprintf("/api/org/users/search?%s", q.Encode()) + + var res struct { + OrgUsers []LegacyUserSearchHit `json:"orgUsers"` + } + + rsp := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.Admin, + Method: "GET", + Path: path, + }, &res) + + require.Equal(t, 200, rsp.Response.StatusCode) + return res.OrgUsers +} + +// verifyOrder checks that the extracted values from hits are in the expected order. +// It filters hits to only include those with expected values, because search returns more results than just the test users. +// Like other users in the system that have been created by the test framework. +func verifyOrder(t *testing.T, hits []iamv0.UserHit, expectedValues []string, extractor func(iamv0.UserHit) string) { + // Filter hits to only include expected values + var actualValues []string + expectedSet := make(map[string]bool) + for _, v := range expectedValues { + expectedSet[v] = true + } + + for _, h := range hits { + val := extractor(h) + if expectedSet[val] { + actualValues = append(actualValues, val) + } + } + + require.Equal(t, expectedValues, actualValues) +} + +func updateLastSeenAt(t *testing.T, helper *apis.K8sTestHelper, login string, lastSeen time.Time, mode rest.DualWriterMode) { + if mode < rest.Mode3 { + err := helper.GetEnv().SQLStore.WithDbSession(context.Background(), func(sess *db.Session) error { + _, err := sess.Table("user").Where("login = ?", login).Update(map[string]interface{}{ + "last_seen_at": lastSeen, + }) + return err + }) + require.NoError(t, err) + } + + // Use the new APIs to update the user resource status in Mode3+ + if mode >= rest.Mode3 { + ctx := context.Background() + userClient := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()), + GVR: gvrUsers, + }) + + u, err := userClient.Resource.Get(ctx, login, metav1.GetOptions{}) + require.NoError(t, err) + + err = unstructured.SetNestedField(u.Object, lastSeen.Unix(), "status", "lastSeenAt") + require.NoError(t, err) + + _, err = userClient.Resource.Update(ctx, u, metav1.UpdateOptions{}) + require.NoError(t, err) + } +} diff --git a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json index ea0e4039581..dab9f3cd8b1 100644 --- a/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/iam.grafana.app-v0alpha1.json @@ -1021,6 +1021,156 @@ } } }, + "/apis/iam.grafana.app/v0alpha1/namespaces/{namespace}/searchUsers": { + "get": { + "tags": [ + "Search" + ], + "description": "User search", + "operationId": "getSearchUsers", + "parameters": [ + { + "name": "namespace", + "in": "path", + "description": "workspace", + "required": true, + "schema": { + "type": "string" + }, + "example": "default" + }, + { + "name": "query", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "description": "number of results to return", + "schema": { + "type": "integer", + "format": "int64" + }, + "example": 30 + }, + { + "name": "page", + "in": "query", + "description": "page number (starting from 1)", + "schema": { + "type": "integer", + "format": "int64" + }, + "example": 1 + }, + { + "name": "offset", + "in": "query", + "description": "number of results to skip", + "schema": { + "type": "integer", + "format": "int64" + }, + "example": 0 + }, + { + "name": "sort", + "in": "query", + "description": "sortable field", + "schema": { + "type": "string" + }, + "examples": { + "": { + "summary": "default sorting" + }, + "-email": { + "summary": "email descending", + "value": "-email" + }, + "-lastSeenAt": { + "summary": "last seen at descending", + "value": "-lastSeenAt" + }, + "-login": { + "summary": "login descending", + "value": "-login" + }, + "-title": { + "summary": "title descending", + "value": "-title" + }, + "email": { + "summary": "email ascending", + "value": "email" + }, + "lastSeenAt": { + "summary": "last seen at ascending", + "value": "lastSeenAt" + }, + "login": { + "summary": "login ascending", + "value": "login" + }, + "title": { + "summary": "title ascending", + "value": "title" + } + } + } + ], + "responses": { + "default": { + "description": "Default OK response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "offset", + "totalHits", + "hits", + "queryCost", + "maxScore" + ], + "properties": { + "hits": { + "type": "array", + "items": { + "default": {} + } + }, + "maxScore": { + "type": "number", + "format": "double", + "default": 0 + }, + "offset": { + "type": "integer", + "format": "int64", + "default": 0 + }, + "queryCost": { + "type": "number", + "format": "double", + "default": 0 + }, + "totalHits": { + "type": "integer", + "format": "int64", + "default": 0 + } + } + } + } + } + } + } + } + }, "/apis/iam.grafana.app/v0alpha1/namespaces/{namespace}/serviceaccounts": { "get": { "tags": [ @@ -5614,7 +5764,8 @@ "type": "object", "required": [ "metadata", - "spec" + "spec", + "status" ], "properties": { "apiVersion": { @@ -5641,6 +5792,14 @@ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.UserSpec" } ] + }, + "status": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.UserStatus" + } + ] } }, "x-kubernetes-group-version-kind": [ @@ -5741,6 +5900,19 @@ } } }, + "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.UserStatus": { + "type": "object", + "required": [ + "lastSeenAt" + ], + "properties": { + "lastSeenAt": { + "type": "integer", + "format": "int64", + "default": 0 + } + } + }, "com.github.grafana.grafana.apps.iam.pkg.apis.iam.v0alpha1.VersionsV0alpha1Kinds7RoutesGroupsGETResponseExternalGroupMapping": { "type": "object", "required": [ @@ -6566,6 +6738,44 @@ } } }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GetSearchUsers": { + "type": "object", + "required": [ + "offset", + "totalHits", + "hits", + "queryCost", + "maxScore" + ], + "properties": { + "hits": { + "type": "array", + "items": { + "default": {} + } + }, + "maxScore": { + "type": "number", + "format": "double", + "default": 0 + }, + "offset": { + "type": "integer", + "format": "int64", + "default": 0 + }, + "queryCost": { + "type": "number", + "format": "double", + "default": 0 + }, + "totalHits": { + "type": "integer", + "format": "int64", + "default": 0 + } + } + }, "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRole": { "type": "object", "required": [ @@ -7760,7 +7970,8 @@ "type": "object", "required": [ "metadata", - "spec" + "spec", + "status" ], "properties": { "apiVersion": { @@ -7777,6 +7988,63 @@ "spec": { "description": "Spec is the spec of the User", "default": {} + }, + "status": { + "default": {} + } + } + }, + "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserHit": { + "type": "object", + "required": [ + "name", + "title", + "login", + "email", + "role", + "lastSeenAt", + "lastSeenAtAge", + "provisioned", + "score" + ], + "properties": { + "email": { + "type": "string", + "default": "" + }, + "lastSeenAt": { + "type": "integer", + "format": "int64", + "default": 0 + }, + "lastSeenAtAge": { + "type": "string", + "default": "" + }, + "login": { + "type": "string", + "default": "" + }, + "name": { + "type": "string", + "default": "" + }, + "provisioned": { + "type": "boolean", + "default": false + }, + "role": { + "type": "string", + "default": "" + }, + "score": { + "type": "number", + "format": "double", + "default": 0 + }, + "title": { + "type": "string", + "default": "" } } }, @@ -7854,51 +8122,15 @@ } }, "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserStatus": { - "type": "object", - "properties": { - "additionalFields": { - "description": "additionalFields is reserved for future use", - "type": "object", - "additionalProperties": { - "type": "object" - } - }, - "operatorStates": { - "description": "operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field.", - "type": "object", - "additionalProperties": { - "default": {} - } - } - } - }, - "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserstatusOperatorState": { "type": "object", "required": [ - "lastEvaluation", - "state" + "lastSeenAt" ], "properties": { - "descriptiveState": { - "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", - "type": "string" - }, - "details": { - "description": "details contains any extra information that is operator-specific", - "type": "object", - "additionalProperties": { - "type": "object" - } - }, - "lastEvaluation": { - "description": "lastEvaluation is the ResourceVersion last evaluated", - "type": "string", - "default": "" - }, - "state": { - "description": "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", - "type": "string", - "default": "" + "lastSeenAt": { + "type": "integer", + "format": "int64", + "default": 0 } } }, From f5b2dde4a16f1d4891ad3378d65c3a2d129364a4 Mon Sep 17 00:00:00 2001 From: Paul Marbach Date: Thu, 11 Dec 2025 14:13:33 -0500 Subject: [PATCH 033/297] Suggestions: Add keyboard support (#114517) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Suggestions: hashes on suggestions, update logic to select first suggestion * fix types * Suggestions: New UI style updates * update some styles * getting styles just right * remove grouping when not on flag * adjust minimum width for sidebar * CI cleanups * updates from ad hoc review * add loading and error states to suggestions * remove unused import * update header ui for panel editor * restore back button to vizpicker * fix e2e test * fix e2e * add i18n update * use new util for setVisualization operation * Apply suggestions from code review Co-authored-by: Torkel Ödegaard * comments from review * updates from review * Suggestions: Add keyboard support * fix selector for PluginVisualization.item --------- Co-authored-by: Torkel Ödegaard --- eslint-suppressions.json | 5 -- .../src/selectors/components.ts | 1 + .../panelcfg/x/TablePanelCfg_types.gen.ts | 4 ++ .../x/TimeSeriesPanelCfg_types.gen.ts | 1 + .../src/components/Table/TableNG/TableNG.tsx | 5 +- .../src/components/Table/TableNG/types.ts | 2 + .../panel-edit/PanelVizTypePicker.tsx | 60 +++++++++++-------- .../LibraryPanelCard/LibraryPanelCard.tsx | 2 +- .../LibraryPanelsSearch.test.tsx | 4 +- .../VizTypePicker/PanelTypeCard.tsx | 24 +++++--- .../VisualizationSuggestionCard.tsx | 3 +- .../VisualizationSuggestions.tsx | 12 +++- .../VizTypePicker/VizTypePicker.tsx | 6 +- .../VizTypePicker/VizTypePickerPlugin.tsx | 8 +-- .../app/plugins/panel/heatmap/suggestions.ts | 11 +++- public/app/plugins/panel/histogram/module.tsx | 12 +++- .../plugins/panel/status-history/module.tsx | 8 ++- public/app/plugins/panel/table/TablePanel.tsx | 8 ++- public/app/plugins/panel/table/panelcfg.cue | 2 + .../app/plugins/panel/table/panelcfg.gen.ts | 4 ++ public/app/plugins/panel/table/suggestions.ts | 2 + .../panel/timeseries/TimeSeriesPanel.tsx | 2 +- .../app/plugins/panel/timeseries/panelcfg.cue | 1 + .../plugins/panel/timeseries/panelcfg.gen.ts | 1 + .../plugins/panel/timeseries/suggestions.ts | 1 + 25 files changed, 132 insertions(+), 57 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index bc54f1c116e..94a0b4c6b3c 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -2882,11 +2882,6 @@ "count": 1 } }, - "public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx": { - "@grafana/no-aria-label-selectors": { - "count": 1 - } - }, "public/app/features/panel/panellinks/linkSuppliers.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index aca18844459..d6e34db61ca 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -1057,6 +1057,7 @@ export const versionedComponents = { }, PluginVisualization: { item: { + '12.4.0': (title: string) => `data-testid Plugin visualization item ${title}`, [MIN_GRAFANA_VERSION]: (title: string) => `Plugin visualization item ${title}`, }, current: { diff --git a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts index c45151fed2b..62c64d3384a 100644 --- a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts @@ -17,6 +17,10 @@ export interface Options { * Controls the height of the rows */ cellHeight?: ui.TableCellHeight; + /** + * If true, disables all keyboard events in the table. this is used when previewing a table (i.e. suggestions) + */ + disableKeyboardEvents?: boolean; /** * Enable pagination on the table */ diff --git a/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts index d0213051004..6cf92ecb15d 100644 --- a/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts @@ -13,6 +13,7 @@ import * as common from '@grafana/schema'; export const pluginVersion = "12.4.0-pre"; export interface Options extends common.OptionsWithTimezones, common.OptionsWithAnnotations { + disableKeyboardEvents?: boolean; legend: common.VizLegendOptions; orientation?: common.VizOrientation; timeCompare?: common.TimeCompareOptions; diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index 798ca4e1ae0..0a7c69edbf3 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -105,6 +105,7 @@ export function TableNG(props: TableNGProps) { const { cellHeight, data, + disableKeyboardEvents, disableSanitizeHtml, enablePagination = false, enableSharedCrosshair = false, @@ -819,9 +820,9 @@ export function TableNG(props: TableNGProps) { } }} onCellKeyDown={ - hasNestedFrames + hasNestedFrames || disableKeyboardEvents ? (_, event) => { - if (event.isDefaultPrevented()) { + if (disableKeyboardEvents || event.isDefaultPrevented()) { // skip parent grid keyboard navigation if nested grid handled it event.preventGridDefault(); } diff --git a/packages/grafana-ui/src/components/Table/TableNG/types.ts b/packages/grafana-ui/src/components/Table/TableNG/types.ts index b828dfddbb6..3a641de5ac6 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/types.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/types.ts @@ -138,6 +138,8 @@ export interface BaseTableProps { enableVirtualization?: boolean; // for MarkdownCell, this flag disables sanitization of HTML content. Configured via config.ini. disableSanitizeHtml?: boolean; + // if true, disables all keyboard events in the table. this is used when previewing a table (i.e. suggestions) + disableKeyboardEvents?: boolean; } /* ---------------------------- Table cell props ---------------------------- */ diff --git a/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx b/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx index cb0d9fbcd3a..86024c3769c 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/css'; import { debounce } from 'lodash'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useId, useMemo, useState } from 'react'; import { useSessionStorage } from 'react-use'; import { GrafanaTheme2, PanelData } from '@grafana/data'; @@ -8,7 +8,7 @@ import { selectors } from '@grafana/e2e-selectors'; import { t, Trans } from '@grafana/i18n'; import { config, reportInteraction } from '@grafana/runtime'; import { VizPanel } from '@grafana/scenes'; -import { Button, FilterInput, ScrollContainer, Stack, Tab, TabContent, TabsBar, useStyles2 } from '@grafana/ui'; +import { Button, Field, FilterInput, ScrollContainer, Stack, Tab, TabContent, TabsBar, useStyles2 } from '@grafana/ui'; import { LS_VISUALIZATION_SELECT_TAB_KEY } from 'app/core/constants'; import { VisualizationSelectPaneTab } from 'app/features/dashboard/components/PanelEditor/types'; import { VisualizationSuggestions } from 'app/features/panel/components/VizTypePicker/VisualizationSuggestions'; @@ -44,6 +44,7 @@ const getTabs = (): Array<{ label: string; value: VisualizationSelectPaneTab }> export function PanelVizTypePicker({ panel, data, onChange, onClose, showBackButton }: Props) { const styles = useStyles2(getStyles); const panelModel = useMemo(() => new PanelModelCompatibilityWrapper(panel), [panel]); + const filterId = useId(); /** SEARCH */ const [searchQuery, setSearchQuery] = useState(''); @@ -101,26 +102,36 @@ export function PanelVizTypePicker({ panel, data, onChange, onClose, showBackBut )} {listMode === VisualizationSelectPaneTab.Visualizations && ( - - {showBackButton && ( - - )} - - + + + {showBackButton && ( + + )} + + + + ({ height: '100%', gap: theme.spacing(2), }), - searchRow: css({ - display: 'flex', - marginBottom: theme.spacing(2), + searchField: css({ + marginTop: theme.spacing(0.5), // input glow with the boundary without this }), tabs: css({ width: '100%', diff --git a/public/app/features/library-panels/components/LibraryPanelCard/LibraryPanelCard.tsx b/public/app/features/library-panels/components/LibraryPanelCard/LibraryPanelCard.tsx index 1faa611d888..ebf2c37ea41 100644 --- a/public/app/features/library-panels/components/LibraryPanelCard/LibraryPanelCard.tsx +++ b/public/app/features/library-panels/components/LibraryPanelCard/LibraryPanelCard.tsx @@ -39,7 +39,7 @@ const LibraryPanelCardComponent = ({ libraryPanel, onClick, onDelete, showSecond title={libraryPanel.name} description={libraryPanel.description} plugin={panelPlugin} - onClick={() => onClick?.(libraryPanel)} + onSelect={() => onClick?.(libraryPanel)} onDelete={showSecondaryActions ? () => setShowDeletionModal(true) : undefined} > diff --git a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx index 7cb1a99a864..941dca5416d 100644 --- a/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx +++ b/public/app/features/library-panels/components/LibraryPanelsSearch/LibraryPanelsSearch.test.tsx @@ -252,7 +252,7 @@ describe('LibraryPanelsSearch', () => { } ); - const card = () => screen.getByLabelText(/plugin visualization item time series/i); + const card = () => screen.getByTestId(/plugin visualization item time series/i); expect(screen.queryByText(/you haven\'t created any library panels yet/i)).not.toBeInTheDocument(); expect(card()).toBeInTheDocument(); @@ -293,7 +293,7 @@ describe('LibraryPanelsSearch', () => { } ); - const card = () => screen.getByLabelText(/plugin visualization item time series/i); + const card = () => screen.getByTestId(/plugin visualization item time series/i); expect(screen.queryByText(/you haven\'t created any library panels yet/i)).not.toBeInTheDocument(); expect(card()).toBeInTheDocument(); diff --git a/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx b/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx index 2125e645e60..3a5c55d2c1b 100644 --- a/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx +++ b/public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx @@ -1,5 +1,4 @@ import { css, cx } from '@emotion/css'; -import { MouseEventHandler } from 'react'; import * as React from 'react'; import Skeleton from 'react-loading-skeleton'; @@ -14,11 +13,12 @@ interface Props { isCurrent: boolean; plugin: PanelPluginMeta; title: string; - onClick: MouseEventHandler; + onSelect: (withModKey?: boolean) => void; onDelete?: () => void; disabled?: boolean; showBadge?: boolean; description?: string; + tabIndex?: number; } const IMAGE_SIZE = 38; @@ -27,12 +27,13 @@ const PanelTypeCardComponent = ({ isCurrent, title, plugin, - onClick, + onSelect, onDelete, disabled, showBadge, description, children, + tabIndex = 0, }: React.PropsWithChildren) => { const styles = useStyles2(getStyles); @@ -44,13 +45,22 @@ const PanelTypeCardComponent = ({ }); return ( - // TODO: fix keyboard a11y - // eslint-disable-next-line jsx-a11y/click-events-have-key-events
onSelect(ev.metaKey || ev.ctrlKey || ev.altKey)} + role="button" + tabIndex={0} + onKeyDown={ + isDisabled + ? undefined + : (ev) => { + if (ev.key === 'Enter' || ev.key === ' ') { + ev.preventDefault(); + onSelect(ev.metaKey || ev.ctrlKey || ev.altKey); + } + } + } title={ isCurrent ? t('panel.panel-type-card.title-click-to-close', 'Click again to close this section') : plugin.name } diff --git a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx index cf9fafd0b9b..7248dbcc1e5 100644 --- a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx +++ b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx @@ -34,8 +34,9 @@ export function VisualizationSuggestionCard({ className: cx(className, styles.vizBox), 'data-testid': selectors.components.VisualizationPreview.card(suggestion.name), style: outerStyles, + tabIndex: -1, // selection is handled by parent container ...restProps, - }; + } satisfies HTMLAttributes & { 'data-testid': string }; let content: ReactNode; diff --git a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx index 269f564d833..d7eed4ed2e7 100644 --- a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx +++ b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx @@ -147,10 +147,21 @@ export function VisualizationSuggestions({ onChange, data, panel }: Props) {
{ + if (ev.key === 'Enter' || ev.key === ' ') { + ev.preventDefault(); + applySuggestion(suggestion, isNewVizSuggestionsEnabled && !isCardSelected); + } + }} ref={index === 0 ? firstCardRef : undefined} > {isCardSelected && (
diff --git a/public/app/features/panel/components/VizTypePicker/VizTypePicker.tsx b/public/app/features/panel/components/VizTypePicker/VizTypePicker.tsx index 8bf12da90c8..f9d3ae7d92c 100644 --- a/public/app/features/panel/components/VizTypePicker/VizTypePicker.tsx +++ b/public/app/features/panel/components/VizTypePicker/VizTypePicker.tsx @@ -41,16 +41,16 @@ export function VizTypePicker({ pluginId, searchQuery, onChange, trackSearch }: return (
- {filteredPluginTypes.map((plugin) => ( + {filteredPluginTypes.map((plugin, idx) => ( + onSelect={(withModKey) => onChange({ pluginId: plugin.id, - withModKey: e.metaKey || e.ctrlKey || e.altKey, + withModKey, }) } /> diff --git a/public/app/features/panel/components/VizTypePicker/VizTypePickerPlugin.tsx b/public/app/features/panel/components/VizTypePicker/VizTypePickerPlugin.tsx index 1ece804df3b..fc277b8f77c 100644 --- a/public/app/features/panel/components/VizTypePicker/VizTypePickerPlugin.tsx +++ b/public/app/features/panel/components/VizTypePicker/VizTypePickerPlugin.tsx @@ -1,5 +1,3 @@ -import { MouseEventHandler } from 'react'; - import { PanelPluginMeta } from '@grafana/data'; import { PanelTypeCard } from './PanelTypeCard'; @@ -7,17 +5,17 @@ import { PanelTypeCard } from './PanelTypeCard'; interface Props { isCurrent: boolean; plugin: PanelPluginMeta; - onClick: MouseEventHandler; + onSelect: (withModKey?: boolean) => void; disabled: boolean; } -export const VizTypePickerPlugin = ({ isCurrent, plugin, onClick, disabled }: Props) => { +export const VizTypePickerPlugin = ({ isCurrent, plugin, onSelect, disabled }: Props) => { return ( { + s.options!.legend = { show: false }; + }, + }, + }, + ]; }; diff --git a/public/app/plugins/panel/histogram/module.tsx b/public/app/plugins/panel/histogram/module.tsx index 8855287cc93..3b3254d784e 100644 --- a/public/app/plugins/panel/histogram/module.tsx +++ b/public/app/plugins/panel/histogram/module.tsx @@ -10,7 +10,7 @@ import { DataFrameType, } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { commonOptionsBuilder, getGraphFieldOptions } from '@grafana/ui'; +import { commonOptionsBuilder, getGraphFieldOptions, LegendDisplayMode } from '@grafana/ui'; import { StackingEditor } from '@grafana/ui/internal'; import { HistogramPanel } from './HistogramPanel'; @@ -160,6 +160,16 @@ export const plugin = new PanelPlugin(HistogramPanel) score: ds.hasDataFrameType(DataFrameType.Histogram) ? VisualizationSuggestionScore.Best : VisualizationSuggestionScore.OK, + cardOptions: { + previewModifier: (s) => { + s.options!.legend = { + calcs: [], + displayMode: LegendDisplayMode.Hidden, + placement: 'bottom', + showLegend: false, + }; + }, + }, }, ]; } diff --git a/public/app/plugins/panel/status-history/module.tsx b/public/app/plugins/panel/status-history/module.tsx index eed5e3546a8..f4ea47412ee 100644 --- a/public/app/plugins/panel/status-history/module.tsx +++ b/public/app/plugins/panel/status-history/module.tsx @@ -1,6 +1,6 @@ import { FieldColorModeId, FieldConfigProperty, FieldType, PanelPlugin } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { AxisPlacement, VisibilityMode } from '@grafana/schema'; +import { AxisPlacement, LegendDisplayMode, VisibilityMode } from '@grafana/schema'; import { commonOptionsBuilder } from '@grafana/ui'; import { StatusHistoryPanel } from './StatusHistoryPanel'; @@ -144,6 +144,12 @@ export const plugin = new PanelPlugin(StatusHistoryPanel) { cardOptions: { previewModifier: (s) => { + s.options!.legend = { + displayMode: LegendDisplayMode.Hidden, + placement: 'bottom', + calcs: [], + showLegend: false, + }; s.options!.colWidth = 0.7; }, }, diff --git a/public/app/plugins/panel/table/TablePanel.tsx b/public/app/plugins/panel/table/TablePanel.tsx index 5784ff7dafa..2ac469eddb8 100644 --- a/public/app/plugins/panel/table/TablePanel.tsx +++ b/public/app/plugins/panel/table/TablePanel.tsx @@ -89,6 +89,7 @@ export function TablePanel(props: Props) { structureRev={data.structureRev} transparent={transparent} disableSanitizeHtml={disableSanitizeHtml} + disableKeyboardEvents={options.disableKeyboardEvents} /> ); @@ -107,7 +108,12 @@ export function TablePanel(props: Props) {
{tableElement}
- onChangeTableSelection(val, props)} + />
); diff --git a/public/app/plugins/panel/table/panelcfg.cue b/public/app/plugins/panel/table/panelcfg.cue index 234f91342dd..e72b0192b40 100644 --- a/public/app/plugins/panel/table/panelcfg.cue +++ b/public/app/plugins/panel/table/panelcfg.cue @@ -43,6 +43,8 @@ composableKinds: PanelCfg: { frozenColumns?: { left?: number | *0 } + // If true, disables all keyboard events in the table. this is used when previewing a table (i.e. suggestions) + disableKeyboardEvents?: bool } @cuetsy(kind="interface") FieldConfig: { ui.TableFieldOptions diff --git a/public/app/plugins/panel/table/panelcfg.gen.ts b/public/app/plugins/panel/table/panelcfg.gen.ts index 6f1194bc9d3..64ff0c9476a 100644 --- a/public/app/plugins/panel/table/panelcfg.gen.ts +++ b/public/app/plugins/panel/table/panelcfg.gen.ts @@ -15,6 +15,10 @@ export interface Options { * Controls the height of the rows */ cellHeight?: ui.TableCellHeight; + /** + * If true, disables all keyboard events in the table. this is used when previewing a table (i.e. suggestions) + */ + disableKeyboardEvents?: boolean; /** * Enable pagination on the table */ diff --git a/public/app/plugins/panel/table/suggestions.ts b/public/app/plugins/panel/table/suggestions.ts index 6616d345388..8e0d6e44953 100644 --- a/public/app/plugins/panel/table/suggestions.ts +++ b/public/app/plugins/panel/table/suggestions.ts @@ -21,6 +21,8 @@ export const tableSuggestionsSupplier: VisualizationSuggestionsSupplier { + s.options!.showHeader = false; + s.options!.disableKeyboardEvents = true; if (s.fieldConfig && s.fieldConfig.defaults.custom) { s.fieldConfig.defaults.custom.minWidth = 50; } diff --git a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx index dd51ccdac51..eb3b226a248 100644 --- a/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx +++ b/public/app/plugins/panel/timeseries/TimeSeriesPanel.tsx @@ -145,7 +145,7 @@ export const TimeSeriesPanel = ({ {(uplotConfig, alignedFrame) => { return ( <> - + {!options.disableKeyboardEvents && } {cursorSync !== DashboardCursorSync.Off && ( )} diff --git a/public/app/plugins/panel/timeseries/panelcfg.cue b/public/app/plugins/panel/timeseries/panelcfg.cue index 32960683c92..ff9784f5f44 100644 --- a/public/app/plugins/panel/timeseries/panelcfg.cue +++ b/public/app/plugins/panel/timeseries/panelcfg.cue @@ -31,6 +31,7 @@ composableKinds: PanelCfg: lineage: { timeCompare?: common.TimeCompareOptions orientation?: common.VizOrientation annotations?: common.VizAnnotations + disableKeyboardEvents?: bool } @cuetsy(kind="interface") FieldConfig: common.GraphFieldConfig & {} @cuetsy(kind="interface") diff --git a/public/app/plugins/panel/timeseries/panelcfg.gen.ts b/public/app/plugins/panel/timeseries/panelcfg.gen.ts index 3d39e9c9bdd..b9dec59e240 100644 --- a/public/app/plugins/panel/timeseries/panelcfg.gen.ts +++ b/public/app/plugins/panel/timeseries/panelcfg.gen.ts @@ -11,6 +11,7 @@ import * as common from '@grafana/schema'; export interface Options extends common.OptionsWithTimezones, common.OptionsWithAnnotations { + disableKeyboardEvents?: boolean; legend: common.VizLegendOptions; orientation?: common.VizOrientation; timeCompare?: common.TimeCompareOptions; diff --git a/public/app/plugins/panel/timeseries/suggestions.ts b/public/app/plugins/panel/timeseries/suggestions.ts index a86c43ae232..b3f6abd74ba 100644 --- a/public/app/plugins/panel/timeseries/suggestions.ts +++ b/public/app/plugins/panel/timeseries/suggestions.ts @@ -45,6 +45,7 @@ const withDefaults = ( }, cardOptions: { previewModifier: (s) => { + s.options!.disableKeyboardEvents = true; if (s.fieldConfig?.defaults.custom?.drawStyle !== GraphDrawStyle.Bars) { s.fieldConfig!.defaults.custom!.lineWidth = Math.max(s.fieldConfig!.defaults.custom!.lineWidth ?? 1, 2); } From f8027e4d75064778864e5fa47d723cdf8eb5907f Mon Sep 17 00:00:00 2001 From: Bogdan Matei Date: Thu, 11 Dec 2025 21:17:23 +0200 Subject: [PATCH 034/297] Dashboard: Implement modal to confirm layout change (#111093) --- .../dashboard-group-panels.spec.ts | 6 ++ .../dashboards-panel-layouts.spec.ts | 23 ++++-- .../dashboards-repeats-auto-grid.spec.ts | 26 ++++--- e2e-playwright/dashboard-new-layouts/utils.ts | 10 +++ .../DashboardLayoutSelector.test.tsx | 72 +++++++++++++++++++ .../DashboardLayoutSelector.tsx | 58 ++++++++++----- public/locales/en-US/grafana.json | 8 +++ 7 files changed, 163 insertions(+), 40 deletions(-) create mode 100644 public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.test.tsx diff --git a/e2e-playwright/dashboard-new-layouts/dashboard-group-panels.spec.ts b/e2e-playwright/dashboard-new-layouts/dashboard-group-panels.spec.ts index c35eb03bf84..ad954267c85 100644 --- a/e2e-playwright/dashboard-new-layouts/dashboard-group-panels.spec.ts +++ b/e2e-playwright/dashboard-new-layouts/dashboard-group-panels.spec.ts @@ -419,6 +419,9 @@ test.describe( // Select tabs layout await page.getByLabel('layout-selection-option-Tabs').click(); + // confirm layout change + await dashboardPage.getByGrafanaSelector(selectors.pages.ConfirmModal.delete).click(); + await expect(dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('New row'))).toBeVisible(); await expect(dashboardPage.getByGrafanaSelector(selectors.components.Tab.title('New row 1'))).toBeVisible(); await expect( @@ -757,6 +760,9 @@ test.describe( // Select rows layout await page.getByLabel('layout-selection-option-Rows').click(); + // confirm layout change + await dashboardPage.getByGrafanaSelector(selectors.pages.ConfirmModal.delete).click(); + await dashboardPage .getByGrafanaSelector(selectors.components.DashboardRow.wrapper('New tab 1')) .scrollIntoViewIfNeeded(); diff --git a/e2e-playwright/dashboard-new-layouts/dashboards-panel-layouts.spec.ts b/e2e-playwright/dashboard-new-layouts/dashboards-panel-layouts.spec.ts index ffacaa13912..803f9d18d46 100644 --- a/e2e-playwright/dashboard-new-layouts/dashboards-panel-layouts.spec.ts +++ b/e2e-playwright/dashboard-new-layouts/dashboards-panel-layouts.spec.ts @@ -4,6 +4,8 @@ import { test, expect, E2ESelectorGroups, DashboardPage } from '@grafana/plugin- import testV2Dashboard from '../dashboards/TestV2Dashboard.json'; +import { switchToAutoGrid } from './utils'; + test.use({ featureToggles: { kubernetesDashboards: true, @@ -33,7 +35,8 @@ test.describe( ).toHaveCount(3); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await page.getByLabel('layout-selection-option-Auto grid').click(); + + await switchToAutoGrid(page, dashboardPage); await expect( dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('New panel')) @@ -64,7 +67,8 @@ test.describe( ).toHaveCount(3); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await page.getByLabel('layout-selection-option-Auto grid').click(); + + await switchToAutoGrid(page, dashboardPage); // Get initial positions - standard width should have panels on different rows const firstPanelTop = await getPanelTop(dashboardPage, selectors); @@ -124,7 +128,8 @@ test.describe( ).toHaveCount(3); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await page.getByLabel('layout-selection-option-Auto grid').click(); + + await switchToAutoGrid(page, dashboardPage); await dashboardPage .getByGrafanaSelector(selectors.components.PanelEditor.ElementEditPane.AutoGridLayout.minColumnWidth) @@ -181,7 +186,8 @@ test.describe( ).toHaveCount(3); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await page.getByLabel('layout-selection-option-Auto grid').click(); + + await switchToAutoGrid(page, dashboardPage); await dashboardPage .getByGrafanaSelector(selectors.components.PanelEditor.ElementEditPane.AutoGridLayout.maxColumns) @@ -216,7 +222,8 @@ test.describe( ).toHaveCount(3); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await page.getByLabel('layout-selection-option-Auto grid').click(); + + await switchToAutoGrid(page, dashboardPage); const regularRowHeight = await getPanelHeight(dashboardPage, selectors); @@ -271,7 +278,8 @@ test.describe( ).toHaveCount(3); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await page.getByLabel('layout-selection-option-Auto grid').click(); + + await switchToAutoGrid(page, dashboardPage); const regularRowHeight = await getPanelHeight(dashboardPage, selectors); @@ -328,7 +336,8 @@ test.describe( ).toHaveCount(3); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await page.getByLabel('layout-selection-option-Auto grid').click(); + + await switchToAutoGrid(page, dashboardPage); // Set narrow column width first to ensure panels fit horizontally await dashboardPage diff --git a/e2e-playwright/dashboard-new-layouts/dashboards-repeats-auto-grid.spec.ts b/e2e-playwright/dashboard-new-layouts/dashboards-repeats-auto-grid.spec.ts index dbba5c583c1..3e25adc6c41 100644 --- a/e2e-playwright/dashboard-new-layouts/dashboards-repeats-auto-grid.spec.ts +++ b/e2e-playwright/dashboard-new-layouts/dashboards-repeats-auto-grid.spec.ts @@ -1,6 +1,6 @@ import { Page } from 'playwright-core'; -import { test, expect } from '@grafana/plugin-e2e'; +import { test, expect, DashboardPage } from '@grafana/plugin-e2e'; import testV2DashWithRepeats from '../dashboards/V2DashWithRepeats.json'; @@ -12,6 +12,7 @@ import { getPanelPosition, importTestDashboard, goToEmbeddedPanel, + switchToAutoGrid, } from './utils'; const repeatTitleBase = 'repeat - '; @@ -42,7 +43,7 @@ test.describe( await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click(); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await switchToAutoGrid(page); + await switchToAutoGrid(page, dashboardPage); await dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('New panel')).first().click(); @@ -78,7 +79,8 @@ test.describe( await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click(); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await switchToAutoGrid(page); + await switchToAutoGrid(page, dashboardPage); + await saveDashboard(dashboardPage, page, selectors); await page.reload(); @@ -117,7 +119,7 @@ test.describe( await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click(); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await switchToAutoGrid(page); + await switchToAutoGrid(page, dashboardPage); // select first/original repeat panel to activate edit pane await dashboardPage @@ -148,7 +150,7 @@ test.describe( await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click(); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await switchToAutoGrid(page); + await switchToAutoGrid(page, dashboardPage); await saveDashboard(dashboardPage, page, selectors); await page.reload(); @@ -214,7 +216,7 @@ test.describe( await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click(); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await switchToAutoGrid(page); + await switchToAutoGrid(page, dashboardPage); await saveDashboard(dashboardPage, page, selectors); // loading directly into panel editor @@ -271,7 +273,7 @@ test.describe( await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click(); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await switchToAutoGrid(page); + await switchToAutoGrid(page, dashboardPage); // this moving repeated panel between two normal panels await movePanel(dashboardPage, selectors, `${repeatTitleBase}${repeatOptions.at(0)}`, 'New panel'); @@ -319,7 +321,7 @@ test.describe( await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click(); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await switchToAutoGrid(page); + await switchToAutoGrid(page, dashboardPage); await saveDashboard(dashboardPage, page, selectors); await page.reload(); @@ -382,7 +384,7 @@ test.describe( await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click(); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await switchToAutoGrid(page); + await switchToAutoGrid(page, dashboardPage); await saveDashboard(dashboardPage, page, selectors); await page.reload(); @@ -410,7 +412,7 @@ test.describe( await dashboardPage.getByGrafanaSelector(selectors.components.NavToolbar.editDashboard.editButton).click(); await dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.Sidebar.optionsButton).click(); - await switchToAutoGrid(page); + await switchToAutoGrid(page, dashboardPage); await saveDashboard(dashboardPage, page, selectors); await page.reload(); @@ -462,7 +464,3 @@ test.describe( }); } ); - -async function switchToAutoGrid(page: Page) { - await page.getByLabel('layout-selection-option-Auto grid').click(); -} diff --git a/e2e-playwright/dashboard-new-layouts/utils.ts b/e2e-playwright/dashboard-new-layouts/utils.ts index 83508063ef5..69851994812 100644 --- a/e2e-playwright/dashboard-new-layouts/utils.ts +++ b/e2e-playwright/dashboard-new-layouts/utils.ts @@ -1,5 +1,6 @@ import { Page } from '@playwright/test'; +import { selectors } from '@grafana/e2e-selectors'; import { DashboardPage, E2ESelectorGroups, expect } from '@grafana/plugin-e2e'; import testV2Dashboard from '../dashboards/TestV2Dashboard.json'; @@ -239,3 +240,12 @@ export async function getTabPosition(dashboardPage: DashboardPage, selectors: E2 const boundingBox = await tab.boundingBox(); return boundingBox; } + +export async function switchToAutoGrid(page: Page, dashboardPage: DashboardPage) { + await page.getByLabel('layout-selection-option-Auto grid').click(); + // confirm layout change if applicable + const confirmModal = dashboardPage.getByGrafanaSelector(selectors.pages.ConfirmModal.delete); + if (confirmModal) { + await confirmModal.click(); + } +} diff --git a/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.test.tsx b/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.test.tsx new file mode 100644 index 00000000000..d363a95ddf0 --- /dev/null +++ b/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.test.tsx @@ -0,0 +1,72 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { getPanelPlugin } from '@grafana/data/test'; +import { setPluginImportUtils } from '@grafana/runtime'; +import { SceneGridLayout, VizPanel, SceneVariableSet } from '@grafana/scenes'; + +import { activateFullSceneTree } from '../../utils/test-utils'; +import { DashboardScene } from '../DashboardScene'; +import { DashboardGridItem } from '../layout-default/DashboardGridItem'; +import { DefaultGridLayoutManager } from '../layout-default/DefaultGridLayoutManager'; +import { RowItem } from '../layout-rows/RowItem'; +import { RowsLayoutManager } from '../layout-rows/RowsLayoutManager'; +import { LayoutParent } from '../types/LayoutParent'; + +import { DashboardLayoutSelector } from './DashboardLayoutSelector'; + +const switchLayoutMock = jest.fn(); + +setPluginImportUtils({ + importPanelPlugin: (_) => Promise.resolve(getPanelPlugin({})), + getPanelPluginFromCache: (_) => undefined, +}); + +describe('DashboardLayoutSelector', () => { + it('should show confirmation modal when switching layouts', async () => { + const user = userEvent.setup(); + const scene = buildTestScene(); + const layoutManager = scene.state.body; + (layoutManager.parent as LayoutParent).switchLayout = switchLayoutMock; + + render(); + + await user.click(screen.getByLabelText('layout-selection-option-Tabs')); + + const confirmButton = screen.getByRole('button', { name: 'Change layout' }); + + expect(confirmButton).toBeInTheDocument(); + + await user.click(confirmButton); + expect(switchLayoutMock).toHaveBeenCalled(); + }); +}); + +const buildTestScene = () => { + const scene = new DashboardScene({ + title: 'testScene', + editable: true, + $variables: new SceneVariableSet({ + variables: [], + }), + body: new RowsLayoutManager({ + rows: [ + new RowItem({ + title: 'Row 1', + layout: new DefaultGridLayoutManager({ + grid: new SceneGridLayout({ + children: [ + new DashboardGridItem({ + body: new VizPanel({ key: 'panel-1', pluginId: 'text' }), + }), + ], + }), + }), + }), + ], + }), + }); + + activateFullSceneTree(scene); + return scene; +}; diff --git a/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx b/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx index e959352986a..b32555b32f8 100644 --- a/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx +++ b/public/app/features/dashboard-scene/scene/layouts-shared/DashboardLayoutSelector.tsx @@ -1,8 +1,8 @@ -import { useCallback, useMemo } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { t } from '@grafana/i18n'; import { config } from '@grafana/runtime'; -import { RadioButtonGroup, Box } from '@grafana/ui'; +import { RadioButtonGroup, Box, ConfirmModal } from '@grafana/ui'; import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; @@ -20,6 +20,7 @@ export interface Props { export function DashboardLayoutSelector({ layoutManager }: Props) { const isGridLayout = layoutManager.descriptor.isGridLayout; const options = layoutRegistry.list().filter((layout) => layout.isGridLayout === isGridLayout); + const [newLayout, setNewLayout] = useState(); const disableTabs = useMemo(() => { if (config.featureToggles.unlimitedLayoutsNesting) { @@ -36,16 +37,23 @@ export function DashboardLayoutSelector({ layoutManager }: Props) { return false; }, [layoutManager]); - const onChangeLayout = useCallback( - (newLayout: LayoutRegistryItem) => { - const layoutParent = layoutManager.parent; + const onChangeLayout = useCallback((newLayout: LayoutRegistryItem) => setNewLayout(newLayout), []); - if (layoutParent && isLayoutParent(layoutParent)) { - layoutParent.switchLayout(newLayout.createFromLayout(layoutManager)); - } - }, - [layoutManager] - ); + const onConfirmNewLayout = useCallback(() => { + if (!newLayout) { + return; + } + + const layoutParent = layoutManager.parent; + + if (layoutParent && isLayoutParent(layoutParent)) { + layoutParent.switchLayout(newLayout.createFromLayout(layoutManager)); + } + + setNewLayout(undefined); + }, [newLayout, layoutManager]); + + const onDismissNewLayout = useCallback(() => setNewLayout(undefined), []); const disabledOptions: LayoutRegistryItem[] = []; @@ -66,15 +74,27 @@ export function DashboardLayoutSelector({ layoutManager }: Props) { }); return ( - - + + + + - + ); } export function useLayoutCategory(layoutManager: DashboardLayoutManager) { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 761ed8f51ec..90b047262a3 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -5093,6 +5093,14 @@ "layout": "Layout" }, "continue": "Continue", + "panel": { + "modal": { + "body": "Changing the layout will reset all panel positions and sizes.", + "confirm": "Change layout", + "dismiss": "Cancel", + "title": "Change layout" + } + }, "ungroup-nested-text": "This will ungroup all nested groups.", "ungroup-nested-title": "Ungroup nested groups?" }, From 91a72f2572e81a8489b496855dddcff61a43c696 Mon Sep 17 00:00:00 2001 From: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:21:33 -0600 Subject: [PATCH 035/297] DOCS: Updates to Elasticsearch data source docs (#115021) * created new configure folder, rewrote intro page * updated configure doc * updated query editor * updates to template variables * added troubleshooting doc, fixed heading issues * fix linter issues * added alerting doc * corrected title * final edits * fixed linter issue * added deprecation comment per feedback * ran prettier --- .../datasources/elasticsearch/_index.md | 130 ++---- .../elasticsearch/alerting/index.md | 144 +++++++ .../elasticsearch/annotations/index.md | 124 ++++++ .../configure-elasticsearch-data-source.md | 209 ---------- .../elasticsearch/configure/index.md | 377 ++++++++++++++++++ .../elasticsearch/query-editor/index.md | 73 ++-- .../elasticsearch/template-variables/index.md | 99 +++-- .../elasticsearch/troubleshooting/index.md | 266 ++++++++++++ 8 files changed, 1063 insertions(+), 359 deletions(-) create mode 100644 docs/sources/datasources/elasticsearch/alerting/index.md create mode 100644 docs/sources/datasources/elasticsearch/annotations/index.md delete mode 100644 docs/sources/datasources/elasticsearch/configure-elasticsearch-data-source.md create mode 100644 docs/sources/datasources/elasticsearch/configure/index.md create mode 100644 docs/sources/datasources/elasticsearch/troubleshooting/index.md diff --git a/docs/sources/datasources/elasticsearch/_index.md b/docs/sources/datasources/elasticsearch/_index.md index 1143dbdc68b..ebee19b2dcb 100644 --- a/docs/sources/datasources/elasticsearch/_index.md +++ b/docs/sources/datasources/elasticsearch/_index.md @@ -17,16 +17,6 @@ menuTitle: Elasticsearch title: Elasticsearch data source weight: 325 refs: - configuration: - - pattern: /docs/grafana/ - destination: /docs/grafana//setup-grafana/configure-grafana/#sigv4_auth_enabled - - pattern: /docs/grafana-cloud/ - destination: /docs/grafana//setup-grafana/configure-grafana/#sigv4_auth_enabled - provisioning-grafana: - - pattern: /docs/grafana/ - destination: /docs/grafana//administration/provisioning/#data-sources - - pattern: /docs/grafana-cloud/ - destination: /docs/grafana//administration/provisioning/#data-sources explore: - pattern: /docs/grafana/ destination: /docs/grafana//explore/ @@ -44,12 +34,36 @@ refs: Elasticsearch is a search and analytics engine used for a variety of use cases. You can create many types of queries to visualize logs or metrics stored in Elasticsearch, and annotate graphs with log events stored in Elasticsearch. -The following will help you get started working with Elasticsearch and Grafana: +The following resources will help you get started with Elasticsearch and Grafana: - [What is Elasticsearch?](https://www.elastic.co/guide/en/elasticsearch/reference/current/elasticsearch-intro.html) -- [Configure the Elasticsearch data source](/docs/grafana/latest/datasources/elasticsearch/configure-elasticsearch-data-source/) -- [Elasticsearch query editor](query-editor/) -- [Elasticsearch template variables](template-variables/) +- [Configure the Elasticsearch data source](https://grafana.com/docs/grafana//datasources/elasticsearch/configure/) +- [Elasticsearch query editor](https://grafana.com/docs/grafana//datasources/elasticsearch/query-editor/) +- [Elasticsearch template variables](https://grafana.com/docs/grafana//datasources/elasticsearch/template-variables/) +- [Elasticsearch annotations](https://grafana.com/docs/grafana//datasources/elasticsearch/annotations/) +- [Elasticsearch alerting](https://grafana.com/docs/grafana//datasources/elasticsearch/alerting/) +- [Troubleshooting issues with the Elasticsearch data source](https://grafana.com/docs/grafana//datasources/elasticsearch/troubleshooting/) + +## Key capabilities + +The Elasticsearch data source supports: + +- **Metrics queries:** Aggregate and visualize numeric data using bucket and metric aggregations. +- **Log queries:** Search, filter, and explore log data with Lucene query syntax. +- **Annotations:** Overlay Elasticsearch events on your dashboard graphs. +- **Alerting:** Create alerts based on Elasticsearch query results. + +## Before you begin + +Before you configure the Elasticsearch data source, you need: + +- An Elasticsearch instance (v7.17+, v8.x, or v9.x) +- Network access from Grafana to your Elasticsearch server +- Appropriate user credentials or API keys with read access + +{{< admonition type="note" >}} +If you use Amazon OpenSearch Service (the successor to Amazon Elasticsearch Service), use the [OpenSearch data source](https://grafana.com/docs/grafana//datasources/opensearch/) instead. +{{< /admonition >}} ## Supported Elasticsearch versions @@ -63,86 +77,18 @@ This data source supports these versions of Elasticsearch: - v8.x - v9.x -Our maintenance policy for Elasticsearch data source is aligned with the [Elastic Product End of Life Dates](https://www.elastic.co/support/eol) and we ensure proper functionality for supported versions. If you are using an Elasticsearch with version that is past its end-of-life (EOL), you can still execute queries, but you will receive a notification in the query builder indicating that the version of Elasticsearch you are using is no longer supported. It's important to note that in such cases, we do not guarantee the correctness of the functionality, and we will not be addressing any related issues. +The Grafana maintenance policy for the Elasticsearch data source aligns with [Elastic Product End of Life Dates](https://www.elastic.co/support/eol). Grafana ensures proper functionality for supported versions only. If you use an EOL version of Elasticsearch, you can still run queries, but the query builder displays a warning. Grafana doesn't guarantee functionality or provide fixes for EOL versions. -## Provision the data source +## Additional resources -You can define and configure the data source in YAML files as part of Grafana's provisioning system. -For more information about provisioning, and for available configuration options, refer to [Provisioning Grafana](ref:provisioning-grafana). +Once you have configured the Elasticsearch data source, you can: -{{< admonition type="note" >}} -The previously used `database` field has now been [deprecated](https://github.com/grafana/grafana/pull/58647). -You should now use the `index` field in `jsonData` to store the index name. -Please see the examples below. -{{< /admonition >}} +- Use [Explore](ref:explore) to run ad-hoc queries against your Elasticsearch data. +- Configure and use [template variables](https://grafana.com/docs/grafana//datasources/elasticsearch/template-variables/) for dynamic dashboards. +- Add [Transformations](https://grafana.com/docs/grafana//panels-visualizations/query-transform-data/transform-data/) to process query results. +- [Build dashboards](ref:build-dashboards) to visualize your Elasticsearch data. -### Provisioning examples +## Related data sources -**Basic provisioning** - -```yaml -apiVersion: 1 - -datasources: - - name: Elastic - type: elasticsearch - access: proxy - url: http://localhost:9200 - jsonData: - index: '[metrics-]YYYY.MM.DD' - interval: Daily - timeField: '@timestamp' -``` - -**Provision for logs** - -```yaml -apiVersion: 1 - -datasources: - - name: elasticsearch-v7-filebeat - type: elasticsearch - access: proxy - url: http://localhost:9200 - jsonData: - index: '[filebeat-]YYYY.MM.DD' - interval: Daily - timeField: '@timestamp' - logMessageField: message - logLevelField: fields.level - dataLinks: - - datasourceUid: my_jaeger_uid # Target UID needs to be known - field: traceID - url: '$${__value.raw}' # Careful about the double "$$" because of env var expansion -``` - -## Configure Amazon Elasticsearch Service - -If you use Amazon Elasticsearch Service, you can use Grafana's Elasticsearch data source to visualize data from it. - -If you use an AWS Identity and Access Management (IAM) policy to control access to your Amazon Elasticsearch Service domain, you must use AWS Signature Version 4 (AWS SigV4) to sign all requests to that domain. - -For details on AWS SigV4, refer to the [AWS documentation](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). - -### AWS Signature Version 4 authentication - -To sign requests to your Amazon Elasticsearch Service domain, you can enable SigV4 in Grafana's [configuration](ref:configuration). - -Once AWS SigV4 is enabled, you can configure it on the Elasticsearch data source configuration page. -For more information about AWS authentication options, refer to [AWS authentication](../aws-cloudwatch/aws-authentication/). - -{{< figure src="/static/img/docs/v73/elasticsearch-sigv4-config-editor.png" max-width="500px" class="docs-image--no-shadow" caption="SigV4 configuration for AWS Elasticsearch Service" >}} - -## Query the data source - -You can select multiple metrics and group by multiple terms or filters when using the Elasticsearch query editor. - -For details, see the [query editor documentation](query-editor/). - -## Use template variables - -Instead of hard-coding details such as server, application, and sensor names in metric queries, you can use variables. -Grafana lists these variables in dropdown select boxes at the top of the dashboard to help you change the data displayed in your dashboard. -Grafana refers to such variables as template variables. - -For details, see the [template variables documentation](template-variables/). +- [OpenSearch](https://grafana.com/docs/grafana//datasources/opensearch/) - For Amazon OpenSearch Service. +- [Loki](https://grafana.com/docs/grafana//datasources/loki/) - Grafana's log aggregation system. diff --git a/docs/sources/datasources/elasticsearch/alerting/index.md b/docs/sources/datasources/elasticsearch/alerting/index.md new file mode 100644 index 00000000000..ef002764bda --- /dev/null +++ b/docs/sources/datasources/elasticsearch/alerting/index.md @@ -0,0 +1,144 @@ +--- +aliases: + - ../../data-sources/elasticsearch/alerting/ +description: Using Grafana Alerting with the Elasticsearch data source +keywords: + - grafana + - elasticsearch + - alerting + - alerts +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Alerting +title: Elasticsearch alerting +weight: 550 +refs: + alerting: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/ + create-alert-rule: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/alerting-rules/create-grafana-managed-rule/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/alerting-and-irm/alerting/alerting-rules/create-grafana-managed-rule/ +--- + +# Elasticsearch alerting + +You can use Grafana Alerting with Elasticsearch to create alerts based on your Elasticsearch data. This allows you to monitor metrics, detect anomalies, and receive notifications when specific conditions are met. + +For general information about Grafana Alerting, refer to [Grafana Alerting](ref:alerting). + +## Before you begin + +Before creating alerts with Elasticsearch, ensure you have: + +- An Elasticsearch data source configured in Grafana +- Appropriate permissions to create alert rules +- Understanding of the metrics you want to monitor + +## Supported query types + +Elasticsearch alerting works best with **metrics queries** that return time series data. To create a valid alert query: + +- Use a **Date histogram** as the last bucket aggregation (under **Group by**) +- Select appropriate metric aggregations (Count, Average, Sum, Min, Max, etc.) + +Queries that return time series data allow Grafana to evaluate values over time and trigger alerts when thresholds are crossed. + +### Query types and alerting compatibility + +| Query type | Alerting support | Notes | +| ------------------------------ | ---------------- | ----------------------------------------------------------- | +| Metrics with Date histogram | ✅ Full support | Recommended for alerting | +| Metrics without Date histogram | ⚠️ Limited | May not evaluate correctly over time | +| Logs | ❌ Not supported | Use metrics queries instead | +| Raw data | ❌ Not supported | Use metrics queries instead | +| Raw document (deprecated) | ❌ Not supported | Deprecated since Grafana v10.1. Use metrics queries instead | + +## Create an alert rule + +To create an alert rule using Elasticsearch: + +1. Navigate to **Alerting** > **Alert rules**. +1. Click **New alert rule**. +1. Enter a name for the alert rule. +1. Select your **Elasticsearch** data source. +1. Build your query using the query editor: + - Add metric aggregations (for example, Average, Count, Sum) + - Add a Date histogram under **Group by** + - Optionally add filters using Lucene query syntax +1. Configure the alert condition (for example, when the average is above a threshold). +1. Set the evaluation interval and pending period. +1. Configure notifications and labels. +1. Click **Save rule**. + +For detailed instructions, refer to [Create a Grafana-managed alert rule](ref:create-alert-rule). + +## Example alert queries + +The following examples show common alerting scenarios with Elasticsearch. + +### Alert on high error count + +Monitor the number of error-level log entries: + +1. **Query:** `level:error` +1. **Metric:** Count +1. **Group by:** Date histogram (interval: 1m) +1. **Condition:** When count is above 100 + +### Alert on average response time + +Monitor API response times: + +1. **Query:** `type:api_request` +1. **Metric:** Average on field `response_time` +1. **Group by:** Date histogram (interval: 5m) +1. **Condition:** When average is above 500 (milliseconds) + +### Alert on unique user count drop + +Detect drops in active users: + +1. **Query:** `*` (all documents) +1. **Metric:** Unique count on field `user_id` +1. **Group by:** Date histogram (interval: 1h) +1. **Condition:** When unique count is below 100 + +## Limitations + +When using Elasticsearch with Grafana Alerting, be aware of the following limitations: + +### Template variables not supported + +Alert queries cannot contain template variables. Grafana evaluates alert rules on the backend without dashboard context, so variables like `$hostname` or `$environment` won't be resolved. + +If your dashboard query uses template variables, create a separate query for alerting with hard coded values. + +### Logs queries not supported + +Queries using the **Logs** metric type cannot be used for alerting. Convert your query to use metric aggregations with a Date histogram instead. + +### Query complexity + +Complex queries with many nested aggregations may timeout or fail to evaluate. Simplify queries for alerting by: + +- Reducing the number of bucket aggregations +- Using appropriate time intervals +- Adding filters to limit the data scanned + +## Best practices + +Follow these best practices when creating Elasticsearch alerts: + +- **Use specific filters:** Add Lucene query filters to focus on relevant data and improve query performance. +- **Choose appropriate intervals:** Match the Date histogram interval to your evaluation frequency. +- **Test queries first:** Verify your query returns expected results in Explore before creating an alert. +- **Set realistic thresholds:** Base alert thresholds on historical data patterns. +- **Use meaningful names:** Give alert rules descriptive names that indicate what they monitor. diff --git a/docs/sources/datasources/elasticsearch/annotations/index.md b/docs/sources/datasources/elasticsearch/annotations/index.md new file mode 100644 index 00000000000..788cfe15ff6 --- /dev/null +++ b/docs/sources/datasources/elasticsearch/annotations/index.md @@ -0,0 +1,124 @@ +--- +aliases: + - ../../data-sources/elasticsearch/annotations/ +description: Using annotations with Elasticsearch in Grafana +keywords: + - grafana + - elasticsearch + - annotations + - events +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Annotations +title: Elasticsearch annotations +weight: 500 +refs: + annotate-visualizations: + - pattern: /docs/grafana/ + destination: /docs/grafana//dashboards/build-dashboards/annotate-visualizations/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//dashboards/build-dashboards/annotate-visualizations/ +--- + +# Elasticsearch annotations + +Annotations overlay event data on your dashboard graphs, helping you correlate log events with metrics. +You can use Elasticsearch as a data source for annotations to display events such as deployments, alerts, or other significant occurrences on your visualizations. + +For general information about annotations, refer to [Annotate visualizations](ref:annotate-visualizations). + +## Before you begin + +Before creating Elasticsearch annotations, ensure you have: + +- An Elasticsearch data source configured in Grafana +- Documents in Elasticsearch containing event data with timestamp fields +- Read access to the Elasticsearch index containing your events + +## Create an annotation query + +To add an Elasticsearch annotation to your dashboard: + +1. Navigate to your dashboard and click **Dashboard settings** (gear icon). +1. Select **Annotations** in the left menu. +1. Click **Add annotation query**. +1. Enter a **Name** for the annotation. +1. Select your **Elasticsearch** data source from the **Data source** drop-down. +1. Configure the annotation query and field mappings. +1. Click **Save dashboard**. + +## Query + +Use the query field to filter which Elasticsearch documents appear as annotations. The query uses [Lucene query syntax](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax). + +**Examples:** + +| Query | Description | +| ---------------------------------------- | ---------------------------------------------------- | +| `*` | Matches all documents. | +| `type:deployment` | Shows only deployment events. | +| `level:error OR level:critical` | Shows error and critical events. | +| `service:api AND environment:production` | Shows events for a specific service and environment. | +| `tags:release` | Shows events tagged as releases. | + +You can use template variables in your annotation queries. For example, `service:$service` filters annotations based on the selected service variable. + +## Field mappings + +Field mappings tell Grafana which Elasticsearch fields contain the annotation data. + +### Time + +The **Time** field specifies which field contains the annotation timestamp. + +- **Default:** `@timestamp` +- **Format:** The field must contain a date value that Elasticsearch recognizes. + +### Time End + +The **Time End** field specifies a field containing the end time for range annotations. Range annotations display as a shaded region on the graph instead of a single vertical line. + +- **Default:** Empty (single-point annotations) +- **Use case:** Display maintenance windows, incidents, or any event with a duration. + +### Text + +The **Text** field specifies which field contains the annotation description displayed when you hover over the annotation. + +- **Default:** `tags` +- **Tip:** Use a descriptive field like `message`, `description`, or `summary`. + +### Tags + +The **Tags** field specifies which field contains tags for the annotation. Tags help categorize and filter annotations. + +- **Default:** Empty +- **Format:** The field can contain either a comma-separated string or an array of strings. + +## Example: Deployment annotations + +To display deployment events as annotations: + +1. Create an annotation query with the following settings: + - **Query:** `type:deployment` + - **Time:** `@timestamp` + - **Text:** `message` + - **Tags:** `environment` + +This configuration displays deployment events with their messages as the annotation text and environments as tags. + +## Example: Range annotations for incidents + +To display incidents with duration: + +1. Create an annotation query with the following settings: + - **Query:** `type:incident` + - **Time:** `start_time` + - **Time End:** `end_time` + - **Text:** `description` + - **Tags:** `severity` + +This configuration displays incidents as shaded regions from their start time to end time. diff --git a/docs/sources/datasources/elasticsearch/configure-elasticsearch-data-source.md b/docs/sources/datasources/elasticsearch/configure-elasticsearch-data-source.md deleted file mode 100644 index d43e0ec2978..00000000000 --- a/docs/sources/datasources/elasticsearch/configure-elasticsearch-data-source.md +++ /dev/null @@ -1,209 +0,0 @@ ---- -aliases: - - ../data-sources/elasticsearch/ - - ../features/datasources/elasticsearch/ -description: Guide for configuring the Elasticsearch data source in Grafana -keywords: - - grafana - - elasticsearch - - guide - - data source -labels: - products: - - cloud - - enterprise - - oss -menuTitle: Configure Elasticsearch -title: Configure the Elasticsearch data source -weight: 200 -refs: - administration-documentation: - - pattern: /docs/grafana/ - destination: /docs/grafana//administration/data-source-management/ - - pattern: /docs/grafana-cloud/ - destination: /docs/grafana//administration/data-source-management/ - supported-expressions: - - pattern: /docs/grafana/ - destination: /docs/grafana//explore/logs-integration/#log-level - - pattern: /docs/grafana-cloud/ - destination: /docs/grafana//explore/logs-integration/#log-level - query-and-transform-data: - - pattern: /docs/grafana/ - destination: /docs/grafana//panels-visualizations/query-transform-data/ - - pattern: /docs/grafana-cloud/ - destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/ - provisioning-data-source: - - pattern: /docs/grafana/ - destination: /docs/grafana//datasources/elasticsearch/#provision-the-data-source - - pattern: /docs/grafana-cloud/ - destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/elasticsearch/#provision-the-data-source ---- - -# Configure the Elasticsearch data source - -Grafana ships with built-in support for Elasticsearch. -You can create a variety of queries to visualize logs or metrics stored in Elasticsearch, and annotate graphs with log events stored in Elasticsearch. - -For instructions on how to add a data source to Grafana, refer to the [administration documentation](ref:administration-documentation). - -Only users with the organization `administrator` role can add data sources. -Administrators can also [configure the data source via YAML](ref:provisioning-data-source) with Grafana's provisioning system. - -## Configuring permissions - -When Elasticsearch security features are enabled, it is essential to configure the necessary cluster privileges to ensure seamless operation. Below is a list of the required privileges along with their purposes: - -- **monitor** - Necessary to retrieve the version information of the connected Elasticsearch instance. -- **view_index_metadata** - Required for accessing mapping definitions of indices. -- **read** - Grants the ability to perform search and retrieval operations on indices. This is essential for querying and extracting data from the cluster. - -## Add the data source - -To add the Elasticsearch data source, complete the following steps: - -1. Click **Connections** in the left-side menu. -1. Under **Connections**, click **Add new connection**. -1. Enter `Elasticsearch` in the search bar. -1. Click **Elasticsearch** under the **Data source** section. -1. Click **Add new data source** in the upper right. - -You will be taken to the **Settings** tab where you will set up your Elasticsearch configuration. - -## Configuration options - -The following is a list of configuration options for Elasticsearch. - -The first option to configure is the name of your connection: - -- **Name** - The data source name. This is how you refer to the data source in panels and queries. Examples: elastic-1, elasticsearch_metrics. - -- **Default** - Toggle to select as the default data source option. When you go to a dashboard panel or Explore, this will be the default selected data source. - -## Connection - -Connect the Elasticsearch data source by specifying a URL. - -- **URL** - The URL of your Elasticsearch server. If your Elasticsearch server is local, use `http://localhost:9200`. If it is on a server within a network, this is the URL with the port where you are running Elasticsearch. Example: `http://elasticsearch.example.orgname:9200`. - -## Authentication - -There are several authentication methods you can choose in the Authentication section. -Select one of the following authentication methods from the dropdown menu. - -- **Basic authentication** - The most common authentication method. Use your `data source` user name and `data source` password to connect. - -- **Forward OAuth identity** - Forward the OAuth access token (and the OIDC ID token if available) of the user querying the data source. - -- **No authentication** - Make the data source available without authentication. Grafana recommends using some type of authentication method. - - - -### TLS settings - -{{< admonition type="note" >}} -Use TLS (Transport Layer Security) for an additional layer of security when working with Elasticsearch. For information on setting up TLS encryption with Elasticsearch see [Configure TLS](https://www.elastic.co/guide/en/elasticsearch/reference/8.8/configuring-tls.html#configuring-tls). You must add TLS settings to your Elasticsearch configuration file **prior** to setting these options in Grafana. -{{< /admonition >}} - -- **Add self-signed certificate** - Check the box to authenticate with a CA certificate. Follow the instructions of the CA (Certificate Authority) to download the certificate file. Required for verifying self-signed TLS certificates. - -- **TLS client authentication** - Check the box to authenticate with the TLS client, where the server authenticates the client. Add the `Server name`, `Client certificate` and `Client key`. The **ServerName** is used to verify the hostname on the returned certificate. The **Client certificate** can be generated from a Certificate Authority (CA) or be self-signed. The **Client key** can also be generated from a Certificate Authority (CA) or be self-signed. The client key encrypts the data between client and server. - -- **Skip TLS certificate validation** - Check the box to bypass TLS certificate validation. Skipping TLS certificate validation is not recommended unless absolutely necessary or for testing purposes. - -### HTTP headers - -Click **+ Add header** to add one or more HTTP headers. HTTP headers pass additional context and metadata about the request/response. - -- **Header** - Add a custom header. This allows custom headers to be passed based on the needs of your Elasticsearch instance. - -- **Value** - The value of the header. - -## Additional settings - -Additional settings are optional settings that can be configured for more control over your data source. - -### Advanced HTTP settings - -- **Allowed cookies** - Specify cookies by name that should be forwarded to the data source. The Grafana proxy deletes all forwarded cookies by default. - -- **Timeout** - The HTTP request timeout. This must be in seconds. There is no default, so this setting is up to you. - -### Elasticsearch details - -The following settings are specific to the Elasticsearch data source. - -- **Index name** - Use the index settings to specify a default for the `time field` and your Elasticsearch index's name. You can use a time pattern, for example `[logstash-]YYYY.MM.DD`, or a wildcard for the index name. When specifying a time pattern, the fixed part(s) of the pattern should be wrapped in square brackets. - -- **Pattern** - Select the matching pattern if using one in your index name. Options include: - - no pattern - - hourly - - daily - - weekly - - monthly - - yearly - -Only select a pattern option if you have specified a time pattern in the Index name field. - -- **Time field name** - Name of the time field. The default value is @timestamp. You can enter a different name. - -- **Max concurrent shard requests** - Sets the number of shards being queried at the same time. The default is `5`. For more information on shards see [Elasticsearch's documentation](https://www.elastic.co/guide/en/elasticsearch/reference/8.9/scalability.html#scalability). - -- **Min time interval** - Defines a lower limit for the auto group-by time interval. This value **must** be formatted as a number followed by a valid time identifier: - - | Identifier | Description | - | ---------- | ----------- | - | `y` | year | - | `M` | month | - | `w` | week | - | `d` | day | - | `h` | hour | - | `m` | minute | - | `s` | second | - | `ms` | millisecond | - -We recommend setting this value to match your Elasticsearch write frequency. -For example, set this to `1m` if Elasticsearch writes data every minute. - -You can also override this setting in a dashboard panel under its data source options. The default is `10s`. - -- **X-Pack enabled** - Toggle to enable `X-Pack`-specific features and options, which provide the [query editor](../query-editor/) with additional aggregations, such as `Rate` and `Top Metrics`. - -- **Include frozen indices** - Toggle on when the `X-Pack enabled` setting is active. Includes frozen indices in searches. You can configure Grafana to include [frozen indices](https://www.elastic.co/guide/en/elasticsearch/reference/7.13/frozen-indices.html) when performing search requests. - -{{< admonition type="note" >}} -Frozen indices are [deprecated in Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/frozen-indices.html) since v7.14. -{{< /admonition >}} - -- **Default query mode** - Specifies which query mode the data source uses by default. Options are `Metrics`, `Logs`, `Raw data`, and `Raw document`. The default is `Metrics`. - -### Logs - -In this section you can configure which fields the data source uses for log messages and log levels. - -- **Message field name:** - Grabs the actual log message from the default source. - -- **Level field name:** - Name of the field with log level/severity information. When a level label is specified, the value of this label is used to determine the log level and update the color of each log line accordingly. If the log doesn’t have a specified level label, we try to determine if its content matches any of the [supported expressions](ref:supported-expressions). The first match always determines the log level. If Grafana cannot infer a log-level field, it will be visualized with an unknown log level. - -### Data links - -Data links create a link from a specified field that can be accessed in Explore's logs view. You can add multiple data links by clicking **+ Add**. - -Each data link configuration consists of: - -- **Field** - Sets the name of the field used by the data link. - -- **URL/query** - Sets the full link URL if the link is external. If the link is internal, this input serves as a query for the target data source.
In both cases, you can interpolate the value from the field with the `${__value.raw }` macro. - -- **URL Label** (Optional) - Sets a custom display label for the link. The link label defaults to the full external URL or name of the linked internal data source and is overridden by this setting. - -- **Internal link** - Toggle on to set an internal link. For an internal link, you can select the target data source with a data source selector. This supports only tracing data sources. - -## Private data source connect (PDC) and Elasticsearch - -Use private data source connect (PDC) to connect to and query data within a secure network without opening that network to inbound traffic from Grafana Cloud. See [Private data source connect](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/) for more information on how PDC works and [Configure Grafana private data source connect (PDC)](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/configure-pdc/#configure-grafana-private-data-source-connect-pdc) for steps on setting up a PDC connection. - -If you use PDC with SIGv4 (AWS Signature Version 4 Authentication), the PDC agent must allow internet egress to`sts..amazonaws.com:443`. - -- **Private data source connect** - Click in the box to set the default PDC connection from the dropdown menu or create a new connection. - -Once you have configured your Elasticsearch data source options, click **Save & test** at the bottom to test out your data source connection. You can also remove a connection by clicking **Delete**. diff --git a/docs/sources/datasources/elasticsearch/configure/index.md b/docs/sources/datasources/elasticsearch/configure/index.md new file mode 100644 index 00000000000..d76a90855ea --- /dev/null +++ b/docs/sources/datasources/elasticsearch/configure/index.md @@ -0,0 +1,377 @@ +--- +aliases: + - ../configure-elasticsearch-data-source/ +description: Guide for configuring the Elasticsearch data source in Grafana +keywords: + - grafana + - elasticsearch + - guide + - data source +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Configure +title: Configure the Elasticsearch data source +weight: 200 +refs: + administration-documentation: + - pattern: /docs/grafana/ + destination: /docs/grafana//administration/data-source-management/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//administration/data-source-management/ + supported-expressions: + - pattern: /docs/grafana/ + destination: /docs/grafana//explore/logs-integration/#log-level + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//explore/logs-integration/#log-level + query-and-transform-data: + - pattern: /docs/grafana/ + destination: /docs/grafana//panels-visualizations/query-transform-data/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/visualizations/panels-visualizations/query-transform-data/ + provisioning-data-source: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/elasticsearch/configure/#provision-the-data-source + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/connect-externally-hosted/data-sources/elasticsearch/configure/#provision-the-data-source + configuration: + - pattern: /docs/grafana/ + destination: /docs/grafana//setup-grafana/configure-grafana/#sigv4_auth_enabled + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//setup-grafana/configure-grafana/#sigv4_auth_enabled + provisioning-grafana: + - pattern: /docs/grafana/ + destination: /docs/grafana//administration/provisioning/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//administration/provisioning/ +--- + +# Configure the Elasticsearch data source + +Grafana ships with built-in support for Elasticsearch. +You can create a variety of queries to visualize logs or metrics stored in Elasticsearch, and annotate graphs with log events stored in Elasticsearch. + +For instructions on how to add a data source to Grafana, refer to the [administration documentation](ref:administration-documentation). +Administrators can also [configure the data source via YAML](ref:provisioning-data-source) with Grafana's provisioning system. + +## Before you begin + +To configure the Elasticsearch data source, you need: + +- **Grafana administrator permissions:** Only users with the organization `administrator` role can add data sources. +- **A supported Elasticsearch version:** v7.17 or later, v8.x, or v9.x. Elastic Cloud Serverless isn't supported. +- **Elasticsearch server URL:** The HTTP or HTTPS endpoint for your Elasticsearch instance, including the port (default: `9200`). +- **Authentication credentials:** Depending on your Elasticsearch security configuration, you need one of the following: + - Username and password for basic authentication + - API key + - No credentials (if Elasticsearch security is disabled) +- **Network access:** Grafana must be able to reach your Elasticsearch server. For Grafana Cloud, consider using [Private data source connect (PDC)](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/) if your Elasticsearch instance is in a private network. + +## Elasticsearch permissions + +When Elasticsearch security features are enabled, you must configure the following cluster privileges for the user or API key that Grafana uses to connect: + +- **monitor** - Necessary to retrieve the version information of the connected Elasticsearch instance. +- **view_index_metadata** - Required for accessing mapping definitions of indices. +- **read** - Grants the ability to perform search and retrieval operations on indices. This is essential for querying and extracting data from the cluster. + +## Add the data source + +To add the Elasticsearch data source, complete the following steps: + +1. Click **Connections** in the left-side menu. +1. Under **Connections**, click **Add new connection**. +1. Enter `Elasticsearch` in the search bar. +1. Click **Elasticsearch** under the **Data source** section. +1. Click **Add new data source** in the upper right. + +You will be taken to the **Settings** tab where you will set up your Elasticsearch configuration. + +## Configuration options + +Configure the following basic settings for the Elasticsearch data source: + +- **Name** - The data source name. This is how you refer to the data source in panels and queries. Examples: `elastic-1`, `elasticsearch_metrics`. + +- **Default** - Toggle on to make this the default data source. New panels and Explore queries use the default data source. + +## Connection + +- **URL** - The URL of your Elasticsearch server, including the port. Examples: `http://localhost:9200`, `http://elasticsearch.example.com:9200`. + +## Authentication + +Select an authentication method from the drop-down menu: + +- **Basic authentication** - Enter the username and password for your Elasticsearch user. + +- **Forward OAuth identity** - Forward the OAuth access token (and the OIDC ID token if available) of the user querying the data source. + +- **No authentication** - Connect without credentials. Only use this option if your Elasticsearch instance doesn't require authentication. + +### API key authentication + +To authenticate using an Elasticsearch API key, select **No authentication** and configure the API key using HTTP headers: + +1. In the **HTTP headers** section, click **+ Add header**. +1. Set **Header** to `Authorization`. +1. Set **Value** to `ApiKey `, replacing `` with your base64-encoded Elasticsearch API key. + +For information about creating API keys, refer to the [Elasticsearch API keys documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html). + +### Amazon Elasticsearch Service + +If you use Amazon Elasticsearch Service, you can use Grafana's Elasticsearch data source to visualize data from it. + +If you use an AWS Identity and Access Management (IAM) policy to control access to your Amazon Elasticsearch Service domain, you must use AWS Signature Version 4 (AWS SigV4) to sign all requests to that domain. + +For details on AWS SigV4, refer to the [AWS documentation](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). + +To sign requests to your Amazon Elasticsearch Service domain, you can enable SigV4 in Grafana's [configuration](ref:configuration). + +Once AWS SigV4 is enabled, you can configure it on the Elasticsearch data source configuration page. +For more information about AWS authentication options, refer to [AWS authentication](https://grafana.com/docs/grafana//datasources/aws-cloudwatch/aws-authentication/). + +{{< figure src="/static/img/docs/v73/elasticsearch-sigv4-config-editor.png" max-width="500px" class="docs-image--no-shadow" caption="SigV4 configuration for AWS Elasticsearch Service" >}} + +### TLS settings + +{{< admonition type="note" >}} +Use TLS (Transport Layer Security) for an additional layer of security when working with Elasticsearch. For information on setting up TLS encryption with Elasticsearch, refer to [Configure TLS](https://www.elastic.co/guide/en/elasticsearch/reference/8.8/configuring-tls.html#configuring-tls). You must add TLS settings to your Elasticsearch configuration file **prior** to setting these options in Grafana. +{{< /admonition >}} + +- **Add self-signed certificate** - Check the box to authenticate with a CA certificate. Follow the instructions of the CA (Certificate Authority) to download the certificate file. Required for verifying self-signed TLS certificates. + +- **TLS client authentication** - Check the box to authenticate with the TLS client, where the server authenticates the client. Add the `Server name`, `Client certificate` and `Client key`. The **ServerName** is used to verify the hostname on the returned certificate. The **Client certificate** can be generated from a Certificate Authority (CA) or be self-signed. The **Client key** can also be generated from a Certificate Authority (CA) or be self-signed. The client key encrypts the data between client and server. + +- **Skip TLS certificate validation** - Check the box to bypass TLS certificate validation. Skipping TLS certificate validation is not recommended unless absolutely necessary or for testing purposes. + +### HTTP headers + +Click **+ Add header** to add one or more HTTP headers. HTTP headers pass additional context and metadata about the request/response. + +- **Header** - Add a custom header. This allows custom headers to be passed based on the needs of your Elasticsearch instance. + +- **Value** - The value of the header. + +## Additional settings + +Additional settings are optional settings that can be configured for more control over your data source. + +### Advanced HTTP settings + +- **Allowed cookies** - Specify cookies by name that should be forwarded to the data source. The Grafana proxy deletes all forwarded cookies by default. + +- **Timeout** - The HTTP request timeout. This must be in seconds. There is no default, so this setting is up to you. + +### Elasticsearch details + +The following settings are specific to the Elasticsearch data source. + +- **Index name** - The name of your Elasticsearch index. You can use the following formats: + - **Wildcard patterns** - Use `*` to match multiple indices. Examples: `logs-*`, `metrics-*`, `filebeat-*`. + - **Time patterns** - Use date placeholders for time-based indices. Wrap the fixed portion in square brackets. Examples: `[logstash-]YYYY.MM.DD`, `[metrics-]YYYY.MM`. + - **Specific index** - Enter the exact index name. Example: `application-logs`. + +- **Pattern** - Select the matching pattern if you use a time pattern in your index name. Options include: + - no pattern + - hourly + - daily + - weekly + - monthly + - yearly + +Only select a pattern option if you have specified a time pattern in the Index name field. + +- **Time field name** - Name of the time field. The default value is `@timestamp`. You can enter a different name. + +- **Max concurrent shard requests** - Sets the number of shards being queried at the same time. The default is `5`. For more information on shards, refer to the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/8.9/scalability.html#scalability). + +- **Min time interval** - Defines a lower limit for the auto group-by time interval. This value **must** be formatted as a number followed by a valid time identifier: + + | Identifier | Description | + | ---------- | ----------- | + | `y` | year | + | `M` | month | + | `w` | week | + | `d` | day | + | `h` | hour | + | `m` | minute | + | `s` | second | + | `ms` | millisecond | + +We recommend setting this value to match your Elasticsearch write frequency. +For example, set this to `1m` if Elasticsearch writes data every minute. + +You can also override this setting in a dashboard panel under its data source options. The default is `10s`. + +- **X-Pack enabled** - Toggle to enable `X-Pack`-specific features and options, which provide the [query editor](https://grafana.com/docs/grafana//datasources/elasticsearch/query-editor/) with additional aggregations, such as `Rate` and `Top Metrics`. + +- **Include frozen indices** - Toggle on when the `X-Pack enabled` setting is active. Includes frozen indices in searches. You can configure Grafana to include [frozen indices](https://www.elastic.co/guide/en/elasticsearch/reference/7.13/frozen-indices.html) when performing search requests. + +{{< admonition type="note" >}} +Frozen indices are [deprecated in Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/frozen-indices.html) since v7.14. +{{< /admonition >}} + +### Logs + +Configure which fields the data source uses for log messages and log levels. + +- **Message field name** - The field that contains the log message content. + +- **Level field name** - The field that contains log level or severity information. When specified, Grafana uses this field to determine the log level and color-code each log line. If the log doesn't have a level field, Grafana tries to match the content against [supported expressions](ref:supported-expressions). If Grafana can't determine the log level, it displays as unknown. + +### Data links + +Data links create a link from a specified field that can be accessed in Explore's logs view. You can add multiple data links by clicking **+ Add**. + +Each data link configuration consists of: + +- **Field** - Sets the name of the field used by the data link. + +- **URL/query** - Sets the full link URL if the link is external. If the link is internal, this input serves as a query for the target data source.
In both cases, you can interpolate the value from the field with the `${__value.raw }` macro. + +- **URL Label** (Optional) - Sets a custom display label for the link. The link label defaults to the full external URL or name of the linked internal data source and is overridden by this setting. + +- **Internal link** - Toggle on to set an internal link. For an internal link, you can select the target data source with a data source selector. This supports only tracing data sources. + +## Private data source connect (PDC) and Elasticsearch + +Use private data source connect (PDC) to connect to and query data within a secure network without opening that network to inbound traffic from Grafana Cloud. Refer to [Private data source connect](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/) for more information on how PDC works and [Configure Grafana private data source connect (PDC)](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/configure-pdc/#configure-grafana-private-data-source-connect-pdc) for steps on setting up a PDC connection. + +If you use PDC with SigV4 (AWS Signature Version 4 Authentication), the PDC agent must allow internet egress to `sts..amazonaws.com:443`. + +- **Private data source connect** - Click in the box to set the default PDC connection from the drop-down menu or create a new connection. + +Once you have configured your Elasticsearch data source options, click **Save & test** to test the connection. A successful connection displays the following message: + +`Elasticsearch data source is healthy.` + +## Provision the data source + +You can define and configure the data source in YAML files as part of Grafana's provisioning system. +For more information about provisioning, and for available configuration options, refer to [Provisioning Grafana](ref:provisioning-grafana). + +{{< admonition type="note" >}} +The previously used `database` field has now been [deprecated](https://github.com/grafana/grafana/pull/58647). +Use the `index` field in `jsonData` to store the index name. +Refer to the examples below. +{{< /admonition >}} + +### Basic provisioning + +```yaml +apiVersion: 1 + +datasources: + - name: Elastic + type: elasticsearch + access: proxy + url: http://localhost:9200 + jsonData: + index: '[metrics-]YYYY.MM.DD' + interval: Daily + timeField: '@timestamp' +``` + +### Provision for logs + +```yaml +apiVersion: 1 + +datasources: + - name: elasticsearch-v7-filebeat + type: elasticsearch + access: proxy + url: http://localhost:9200 + jsonData: + index: '[filebeat-]YYYY.MM.DD' + interval: Daily + timeField: '@timestamp' + logMessageField: message + logLevelField: fields.level + dataLinks: + - datasourceUid: my_jaeger_uid # Target UID needs to be known + field: traceID + url: '$${__value.raw}' # Careful about the double "$$" because of env var expansion +``` + +## Provision the data source using Terraform + +You can provision the Elasticsearch data source using [Terraform](https://www.terraform.io/) with the [Grafana Terraform provider](https://registry.terraform.io/providers/grafana/grafana/latest/docs). + +For more information about provisioning resources with Terraform, refer to the [Grafana as code using Terraform](https://grafana.com/docs/grafana-cloud/developer-resources/infrastructure-as-code/terraform/) documentation. + +### Basic Terraform example + +The following example creates a basic Elasticsearch data source for metrics: + +```hcl +resource "grafana_data_source" "elasticsearch" { + name = "Elasticsearch" + type = "elasticsearch" + url = "http://localhost:9200" + + json_data_encoded = jsonencode({ + index = "[metrics-]YYYY.MM.DD" + interval = "Daily" + timeField = "@timestamp" + }) +} +``` + +### Terraform example for logs + +The following example creates an Elasticsearch data source configured for logs with a data link to Jaeger: + +```hcl +resource "grafana_data_source" "elasticsearch_logs" { + name = "Elasticsearch Logs" + type = "elasticsearch" + url = "http://localhost:9200" + + json_data_encoded = jsonencode({ + index = "[filebeat-]YYYY.MM.DD" + interval = "Daily" + timeField = "@timestamp" + logMessageField = "message" + logLevelField = "fields.level" + dataLinks = [ + { + datasourceUid = grafana_data_source.jaeger.uid + field = "traceID" + url = "$${__value.raw}" + } + ] + }) +} +``` + +### Terraform example with basic authentication + +The following example includes basic authentication: + +```hcl +resource "grafana_data_source" "elasticsearch_auth" { + name = "Elasticsearch" + type = "elasticsearch" + url = "http://localhost:9200" + + basic_auth_enabled = true + basic_auth_username = "elastic_user" + + secure_json_data_encoded = jsonencode({ + basicAuthPassword = var.elasticsearch_password + }) + + json_data_encoded = jsonencode({ + index = "[metrics-]YYYY.MM.DD" + interval = "Daily" + timeField = "@timestamp" + }) +} +``` + +For all available configuration options, refer to the [Grafana provider data source resource documentation](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/data_source). diff --git a/docs/sources/datasources/elasticsearch/query-editor/index.md b/docs/sources/datasources/elasticsearch/query-editor/index.md index fa20353a395..c5c7bf91abf 100644 --- a/docs/sources/datasources/elasticsearch/query-editor/index.md +++ b/docs/sources/datasources/elasticsearch/query-editor/index.md @@ -30,7 +30,7 @@ refs: # Elasticsearch query editor Grafana provides a query editor for Elasticsearch. Elasticsearch queries are in Lucene format. -See [Lucene query syntax](https://www.elastic.co/guide/en/kibana/current/lucene-query.html) and [Query string syntax](https://www.elastic.co/guide/en/elasticsearch/reference/8.9/query-dsl-query-string-query.html#query-string-syntax) if you are new to working with Lucene queries in Elasticsearch. +For more information about query syntax, refer to [Lucene query syntax](https://www.elastic.co/guide/en/kibana/current/lucene-query.html) and [Query string syntax](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax). {{< admonition type="note" >}} When composing Lucene queries, ensure that you use uppercase boolean operators: `AND`, `OR`, and `NOT`. Lowercase versions of these operators are not supported by the Lucene query syntax. @@ -38,17 +38,17 @@ When composing Lucene queries, ensure that you use uppercase boolean operators: {{< figure src="/static/img/docs/elasticsearch/elastic-query-editor-10.1.png" max-width="800px" class="docs-image--no-shadow" caption="Elasticsearch query editor" >}} -For general documentation on querying data sources in Grafana, including options and functions common to all query editors, see [Query and transform data](ref:query-and-transform-data). +For general documentation on querying data sources in Grafana, including options and functions common to all query editors, refer to [Query and transform data](ref:query-and-transform-data). ## Aggregation types Elasticsearch groups aggregations into three categories: -- **Bucket** - Bucket aggregations don't calculate metrics, they create buckets of documents based on field values, ranges and a variety of other criteria. See [Bucket aggregations](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket.html) for additional information. Use bucket aggregations under `Group by` when creating a metrics query in the query builder. +- **Bucket** - Bucket aggregations don't calculate metrics, they create buckets of documents based on field values, ranges and a variety of other criteria. Refer to [Bucket aggregations](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket.html) for additional information. Use bucket aggregations under `Group by` when creating a metrics query in the query builder. -- **Metrics** - Metrics aggregations perform calculations such as sum, average, min, etc. They can be single-value or multi-value. See [Metrics aggregations](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics.html) for additional information. Use metrics aggregations in the metrics query type in the query builder. +- **Metrics** - Metrics aggregations perform calculations such as sum, average, min, etc. They can be single-value or multi-value. Refer to [Metrics aggregations](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics.html) for additional information. Use metrics aggregations in the metrics query type in the query builder. -- **Pipeline** - Elasticsearch pipeline aggregations work with inputs or metrics created from other aggregations (not documents or fields). There are parent and sibling and sibling pipeline aggregations. See [Pipeline aggregations](https://www.elastic.co/guide/en/elasticsearch/reference/8.9/search-aggregations-pipeline.html) for additional information. +- **Pipeline** - Pipeline aggregations work on the output of other aggregations rather than on documents or fields. Refer to [Pipeline aggregations](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline.html) for additional information. ## Select a query type @@ -56,44 +56,51 @@ There are three types of queries you can create with the Elasticsearch query bui ### Metrics query type -Metrics queries aggregate data and produce a variety of calculations such as count, min, max, etc. Click on the metric box to view a list of options in the dropdown menu. The default is `count`. +Metrics queries aggregate data and produce calculations such as count, min, max, and more. Click the metric box to view options in the drop-down menu. The default is `count`. - **Alias** - Aliasing only applies to **time series queries**, where the last group is `date histogram`. This is ignored for any other type of query. - **Metric** - Metrics aggregations include: - - count - see [Value count aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/8.9/search-aggregations-metrics-valuecount-aggregation.html) - - average - see [Avg aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/8.9/search-aggregations-metrics-rate-aggregation.html) - - sum - see [Sum aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-sum-aggregation.html) - - max - see [Max aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/8.9/search-aggregations-metrics-max-aggregation.html) - - min - see [Min aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/8.9/search-aggregations-metrics-min-aggregation.html) - - extended stats - see [Extended stats aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-extendedstats-aggregation.html) - - percentiles - see [Percentiles aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/8.9/search-aggregations-metrics-percentile-aggregation.html) - - unique count - see [Cardinality aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/8.9/search-aggregations-metrics-cardinality-aggregation.html) - - top metrics - see [Top metrics aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/8.9/search-aggregations-metrics-top-metrics.html) - - rate - see [Rate aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/8.9/search-aggregations-metrics-rate-aggregation.html) + - count - refer to [Value count aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-valuecount-aggregation.html) + - average - refer to [Avg aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-avg-aggregation.html) + - sum - refer to [Sum aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-sum-aggregation.html) + - max - refer to [Max aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-max-aggregation.html) + - min - refer to [Min aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-min-aggregation.html) + - extended stats - refer to [Extended stats aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-extendedstats-aggregation.html) + - percentiles - refer to [Percentiles aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-percentile-aggregation.html) + - unique count - refer to [Cardinality aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-cardinality-aggregation.html) + - top metrics - refer to [Top metrics aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-top-metrics.html) + - rate - refer to [Rate aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-rate-aggregation.html) + +- **Pipeline aggregations** - Pipeline aggregations work on the output of other aggregations rather than on documents. The following pipeline aggregations are available: + - moving function - Calculates a value based on a sliding window of aggregated values. Refer to [Moving function aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline-movfn-aggregation.html). + - derivative - Calculates the derivative of a metric. Refer to [Derivative aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline-derivative-aggregation.html). + - cumulative sum - Calculates the cumulative sum of a metric. Refer to [Cumulative sum aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline-cumulative-sum-aggregation.html). + - serial difference - Calculates the difference between values in a time series. Refer to [Serial differencing aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline-serialdiff-aggregation.html). + - bucket script - Executes a script on metric values from other aggregations. Refer to [Bucket script aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline-bucket-script-aggregation.html). You can select multiple metrics and group by multiple terms or filters when using the Elasticsearch query editor. Use the **+ sign** to the right to add multiple metrics to your query. Click on the **eye icon** next to **Metric** to hide metrics, and the **garbage can icon** to remove metrics. -- **Group by options** - Create multiple group by options when constructing your Elasticsearch query. Date histogram is the default option. Below is a list of options in the dropdown menu. - - terms - see [Terms aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html). - - filter - see [Filter aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filter-aggregation.html). - - geo hash grid - see [Geohash grid aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-geohashgrid-aggregation.html). - - date histogram - for time series queries. See [Date histogram aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html). - - histogram - Depicts frequency distributions. See [Histogram aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-histogram-aggregation.html). - - nested (experimental) - See [Nested aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-nested-aggregation.html). +- **Group by options** - Create multiple group by options when constructing your Elasticsearch query. Date histogram is the default option. The following options are available in the drop-down menu: + - terms - refer to [Terms aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html). + - filter - refer to [Filter aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filter-aggregation.html). + - geo hash grid - refer to [Geohash grid aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-geohashgrid-aggregation.html). + - date histogram - for time series queries. Refer to [Date histogram aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html). + - histogram - Depicts frequency distributions. Refer to [Histogram aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-histogram-aggregation.html). + - nested (experimental) - Refer to [Nested aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-nested-aggregation.html). Each group by option will have a different subset of options to further narrow your query. The following options are specific to the **date histogram** bucket aggregation option. -- **Time field** - Depicts date data options. The default option can be specified when configuring the Elasticsearch data source in the **Time field name** under the [**Elasticsearch details**](/docs/grafana/latest/datasources/elasticsearch/configure-elasticsearch-data-source/#elasticsearch-details) section. Otherwise **@timestamp** field will be used as a default option. -- **Interval** - Group by a type of interval. There are option to choose from the dropdown menu to select seconds, minutes, hours or day. You can also add a custom interval such as `30d` (30 days). `Auto` is the default option. -- **Min doc count** - The minimum amount of data to include in your query. The default is `0`. -- **Thin edges** - Select to trim edges on the time series data points. The default is `0`. -- **Offset** - Changes the start value of each bucket by the specified positive(+) or negative (-) offset duration. Examples include `1h` for 1 hour, `5s` for 5 seconds or `1d` for 1 day. -- **Timezone** - Select a timezone from the dropdown menu. The default is `Coordinated universal time`. +- **Time field** - The field used for time-based queries. The default can be set when configuring the data source in the **Time field name** setting under [Elasticsearch details](https://grafana.com/docs/grafana//datasources/elasticsearch/configure/#elasticsearch-details). The default is `@timestamp`. +- **Interval** - The time interval for grouping data. Select from the drop-down menu or enter a custom interval such as `30d` (30 days). The default is `Auto`. +- **Min doc count** - The minimum number of documents required to include a bucket. The default is `0`. +- **Trim edges** - Removes partial buckets at the edges of the time range. The default is `0`. +- **Offset** - Shifts the start of each bucket by the specified duration. Use positive (`+`) or negative (`-`) values. Examples: `1h`, `5s`, `1d`. +- **Timezone** - The timezone for date calculations. The default is `Coordinated Universal Time`. Configure the following options for the **terms** bucket aggregation option: @@ -101,7 +108,7 @@ Configure the following options for the **terms** bucket aggregation option: - **Size** - Limits the number of documents, or size of the data set. You can set a custom number or `no limit`. - **Min doc count** - The minimum amount of data to include in your query. The default is `0`. - **Order by** - Order terms by `term value`, `doc count` or `count`. -- **Missing** - Defines how documents missing a value should be treated. Missing values are ignored by default, but they can be treated as if they had a value. See [Missing value](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html#_missing_value_5) in Elasticsearch's documentation for more information. +- **Missing** - Defines how documents missing a value should be treated. Missing values are ignored by default, but they can be treated as if they had a value. Refer to [Missing value](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html#_missing_value_5) in the Elasticsearch documentation for more information. Configure the following options for the **filters** bucket aggregation option: @@ -114,8 +121,8 @@ Configure the following options for the **geo hash grid** bucket aggregation opt Configure the following options for the **histogram** bucket aggregation option: -- **Interval** - Group by a type of interval. There are option to choose from the dropdown menu to select seconds, minutes, hours or day. You can also add a custom interval such as `30d` (30 days). `Auto` is the default option. -- **Min doc count** - The minimum amount of data to include in your query. The default is `0` +- **Interval** - The numeric interval for grouping values into buckets. +- **Min doc count** - The minimum number of documents required to include a bucket. The default is `0`. The **nested** group by option is currently experimental, you can select a field and then settings specific to that field. @@ -141,7 +148,7 @@ The option to run a **raw document query** is deprecated as of Grafana v10.1. ## Use template variables -You can also augment queries by using [template variables](../template-variables/). +You can also augment queries by using [template variables](https://grafana.com/docs/grafana//datasources/elasticsearch/template-variables/). Queries of `terms` have a 500-result limit by default. To set a custom limit, set the `size` property in your query. diff --git a/docs/sources/datasources/elasticsearch/template-variables/index.md b/docs/sources/datasources/elasticsearch/template-variables/index.md index 66ca17a93bd..ed2cb95f3af 100644 --- a/docs/sources/datasources/elasticsearch/template-variables/index.md +++ b/docs/sources/datasources/elasticsearch/template-variables/index.md @@ -22,6 +22,11 @@ refs: destination: /docs/grafana//dashboards/variables/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana//dashboards/variables/ + add-template-variables-add-ad-hoc-filters: + - pattern: /docs/grafana/ + destination: /docs/grafana//dashboards/variables/add-template-variables/#add-ad-hoc-filters + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//dashboards/variables/add-template-variables/#add-ad-hoc-filters add-template-variables-multi-value-variables: - pattern: /docs/grafana/ destination: /docs/grafana//dashboards/variables/add-template-variables/#multi-value-variables @@ -37,11 +42,29 @@ refs: # Elasticsearch template variables Instead of hard-coding details such as server, application, and sensor names in metric queries, you can use variables. -Grafana lists these variables in dropdown select boxes at the top of the dashboard to help you change the data displayed in your dashboard. +Grafana lists these variables in drop-down select boxes at the top of the dashboard to help you change the data displayed in your dashboard. Grafana refers to such variables as template variables. For an introduction to templating and template variables, refer to the [Templating](ref:variables) and [Add and manage variables](ref:add-template-variables) documentation. +## Use ad hoc filters + +Elasticsearch supports the **Ad hoc filters** variable type. +You can use this variable type to specify any number of key/value filters, and Grafana applies them automatically to all of your Elasticsearch queries. + +Ad hoc filters support the following operators: + +| Operator | Description | +| -------- | ------------------------------------------------------------- | +| `=` | Equals. Adds `AND field:"value"` to the query. | +| `!=` | Not equals. Adds `AND -field:"value"` to the query. | +| `=~` | Matches regex. Adds `AND field:/value/` to the query. | +| `!~` | Does not match regex. Adds `AND -field:/value/` to the query. | +| `>` | Greater than. Adds `AND field:>value` to the query. | +| `<` | Less than. Adds `AND field:}} -To use an ascending sort (`asc`) with doc_count (a bottom-N list), set `order: "asc"`. However, Elasticsearch [discourages this](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html#search-aggregations-bucket-terms-aggregation-order) because sorting by ascending doc count can return inaccurate results. -{{< /admonition >}} - -To keep terms in the doc count order, set the variable's Sort dropdown to **Disabled**. -You can alternatively use other sorting criteria, such as **Alphabetical**, to re-sort them. +This example defines a variable named `$host` that only shows hosts matching the selected `$environment`: +```json +{ "find": "terms", "field": "hostname", "query": "environment:$environment" } ``` -{"find": "terms", "field": "hostname", "orderBy": "doc_count"} -``` + +Whenever you change the value of the `$environment` variable via the drop-down, Grafana triggers an update of the `$host` variable to contain only hostnames filtered by the selected environment. + +### Variables in aggregations + +You can use variables in bucket aggregation fields to dynamically change how data is grouped. For example, use a variable in the **Terms** group by field to let users switch between grouping by `hostname`, `service`, or `datacenter`. ## Template variable examples @@ -92,11 +116,36 @@ Write the query using a custom JSON string, with the field mapped as a [keyword] If the query is [multi-field](https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-fields.html) with both a `text` and `keyword` type, use `"field":"fieldname.keyword"` (sometimes `fieldname.raw`) to specify the keyword field in your query. -| Query | Description | -| ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `{"find": "fields", "type": "keyword"}` | Returns a list of field names with the index type `keyword`. | -| `{"find": "terms", "field": "hostname.keyword", "size": 1000}` | Returns a list of values for a keyword using term aggregation. Query will use current dashboard time range as time range query. | -| `{"find": "terms", "field": "hostname", "query": ''}` | Returns a list of values for a keyword field using term aggregation and a specified Lucene query filter. Query will use current dashboard time range as time range for query. | +| Query | Description | +| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | +| `{"find": "fields", "type": "keyword"}` | Returns a list of field names with the index type `keyword`. | +| `{"find": "fields", "type": "number"}` | Returns a list of numeric field names (includes `float`, `double`, `integer`, `long`, `scaled_float`). | +| `{"find": "fields", "type": "date"}` | Returns a list of date field names. | +| `{"find": "terms", "field": "hostname.keyword", "size": 1000}` | Returns a list of values for a keyword field. Uses the current dashboard time range. | +| `{"find": "terms", "field": "hostname", "query": ""}` | Returns a list of values filtered by a Lucene query. Uses the current dashboard time range. | +| `{"find": "terms", "field": "status", "orderBy": "doc_count"}` | Returns values sorted by document count (descending by default). | +| `{"find": "terms", "field": "status", "orderBy": "doc_count", "order": "asc"}` | Returns values sorted by document count in ascending order. | -Queries of `terms` have a 500-result limit by default. -To set a custom limit, set the `size` property in your query. +Queries of `terms` have a 500-result limit by default. To set a custom limit, set the `size` property in your query. + +### Sort query results + +By default, queries return results in term order (which can then be sorted alphabetically or numerically using the variable's Sort setting). + +To produce a list of terms sorted by document count (a top-N values list), add an `orderBy` property of `doc_count`. This automatically selects a descending sort: + +```json +{ "find": "terms", "field": "status", "orderBy": "doc_count" } +``` + +You can also use the `order` property to explicitly set ascending or descending sort: + +```json +{ "find": "terms", "field": "hostname", "orderBy": "doc_count", "order": "asc" } +``` + +{{< admonition type="note" >}} +Elasticsearch [discourages](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html#search-aggregations-bucket-terms-aggregation-order) sorting by ascending doc count because it can return inaccurate results. +{{< /admonition >}} + +To keep terms in the document count order, set the variable's Sort drop-down to **Disabled**. You can alternatively use other sorting criteria, such as **Alphabetical**, to re-sort them. diff --git a/docs/sources/datasources/elasticsearch/troubleshooting/index.md b/docs/sources/datasources/elasticsearch/troubleshooting/index.md new file mode 100644 index 00000000000..ff0f73c6093 --- /dev/null +++ b/docs/sources/datasources/elasticsearch/troubleshooting/index.md @@ -0,0 +1,266 @@ +--- +aliases: + - ../../data-sources/elasticsearch/troubleshooting/ +description: Troubleshooting the Elasticsearch data source in Grafana +keywords: + - grafana + - elasticsearch + - troubleshooting + - errors +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Troubleshooting +title: Troubleshoot issues with the Elasticsearch data source +weight: 600 +--- + +# Troubleshoot issues with the Elasticsearch data source + +This document provides troubleshooting information for common errors you may encounter when using the Elasticsearch data source in Grafana. + +## Connection errors + +The following errors occur when Grafana cannot establish or maintain a connection to Elasticsearch. + +### Failed to connect to Elasticsearch + +**Error message:** "Health check failed: Failed to connect to Elasticsearch" + +**Cause:** Grafana cannot establish a network connection to the Elasticsearch server. + +**Solution:** + +1. Verify that the Elasticsearch URL is correct in the data source configuration. +1. Check that Elasticsearch is running and accessible from the Grafana server. +1. Ensure there are no firewall rules blocking the connection. +1. If using a proxy, verify the proxy settings are correct. +1. For Grafana Cloud, ensure you have configured [Private data source connect](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/) if your Elasticsearch instance is not publicly accessible. + +### Request timed out + +**Error message:** "Health check failed: Elasticsearch data source is not healthy. Request timed out" + +**Cause:** The connection to Elasticsearch timed out before receiving a response. + +**Solution:** + +1. Check the network latency between Grafana and Elasticsearch. +1. Verify that Elasticsearch is not overloaded or experiencing performance issues. +1. Increase the timeout setting in the data source configuration if needed. +1. Check if any network devices (load balancers, proxies) are timing out the connection. + +### Failed to parse data source URL + +**Error message:** "Failed to parse data source URL" + +**Cause:** The URL entered in the data source configuration is not valid. + +**Solution:** + +1. Verify the URL format is correct (for example, `http://localhost:9200` or `https://elasticsearch.example.com:9200`). +1. Ensure the URL includes the protocol (`http://` or `https://`). +1. Remove any trailing slashes or invalid characters from the URL. + +## Authentication errors + +The following errors occur when there are issues with authentication credentials or permissions. + +### Unauthorized (401) + +**Error message:** "Health check failed: Elasticsearch data source is not healthy. Status: 401 Unauthorized" + +**Cause:** The authentication credentials are invalid or missing. + +**Solution:** + +1. Verify that the username and password are correct. +1. If using an API key, ensure the key is valid and has not expired. +1. Check that the authentication method selected matches your Elasticsearch configuration. +1. Verify the user has the required permissions to access the Elasticsearch cluster. + +### Forbidden (403) + +**Error message:** "Health check failed: Elasticsearch data source is not healthy. Status: 403 Forbidden" + +**Cause:** The authenticated user does not have permission to access the requested resource. + +**Solution:** + +1. Verify the user has read access to the specified index. +1. Check Elasticsearch security settings and role mappings. +1. Ensure the user has permission to access the `_cluster/health` endpoint. +1. If using AWS Elasticsearch Service with SigV4 authentication, verify the IAM policy grants the required permissions. + +## Cluster health errors + +The following errors occur when the Elasticsearch cluster is unhealthy or unavailable. + +### Cluster status is red + +**Error message:** "Health check failed: Elasticsearch data source is not healthy" + +**Cause:** The Elasticsearch cluster health status is red, indicating one or more primary shards are not allocated. + +**Solution:** + +1. Check the Elasticsearch cluster health using `GET /_cluster/health`. +1. Review Elasticsearch logs for errors. +1. Verify all nodes in the cluster are running and connected. +1. Check for unassigned shards using `GET /_cat/shards?v&h=index,shard,prirep,state,unassigned.reason`. +1. Consider increasing the cluster's resources or reducing the number of shards. + +### Bad Gateway (502) + +**Error message:** "Health check failed: Elasticsearch data source is not healthy. Status: 502 Bad Gateway" + +**Cause:** A proxy or load balancer between Grafana and Elasticsearch returned an error. + +**Solution:** + +1. Check the health of any proxies or load balancers in the connection path. +1. Verify Elasticsearch is running and accepting connections. +1. Review proxy/load balancer logs for more details. +1. Ensure the proxy timeout is configured appropriately for Elasticsearch requests. + +## Index errors + +The following errors occur when there are issues with the configured index or index pattern. + +### Index not found + +**Error message:** "Error validating index: index_not_found" + +**Cause:** The specified index or index pattern does not match any existing indices. + +**Solution:** + +1. Verify the index name or pattern in the data source configuration. +1. Check that the index exists using `GET /_cat/indices`. +1. If using a time-based index pattern (for example, `[logs-]YYYY.MM.DD`), ensure indices exist for the selected time range. +1. Verify the user has permission to access the index. + +### Time field not found + +**Error message:** "Could not find time field '@timestamp' with type date in index" + +**Cause:** The specified time field does not exist in the index or is not of type `date`. + +**Solution:** + +1. Verify the time field name in the data source configuration matches the field in your index. +1. Check the field mapping using `GET //_mapping`. +1. Ensure the time field is mapped as a `date` type, not `text` or `keyword`. +1. If the field name is different (for example, `timestamp` instead of `@timestamp`), update the data source configuration. + +## Query errors + +The following errors occur when there are issues with query syntax or configuration. + +### Too many buckets + +**Error message:** "Trying to create too many buckets. Must be less than or equal to: [65536]." + +**Cause:** The query is generating more aggregation buckets than Elasticsearch allows. + +**Solution:** + +1. Reduce the time range of your query. +1. Increase the date histogram interval (for example, change from `10s` to `1m`). +1. Add filters to reduce the number of documents being aggregated. +1. Increase the `search.max_buckets` setting in Elasticsearch (requires cluster admin access). + +### Required field missing + +**Error message:** "Required one of fields [field, script], but none were specified." + +**Cause:** A metric aggregation (such as Average, Sum, or Min) was added without specifying a field. + +**Solution:** + +1. Select a field for the metric aggregation in the query editor. +1. Ensure the selected field exists in your index and contains numeric data. + +### Unsupported interval + +**Error message:** "unsupported interval '<interval>'" + +**Cause:** The interval specified for the index pattern is not valid. + +**Solution:** + +1. Use a supported interval: `Hourly`, `Daily`, `Weekly`, `Monthly`, or `Yearly`. +1. If you don't need a time-based index pattern, use `No pattern` and specify the exact index name. + +## Version errors + +The following errors occur when there are Elasticsearch version compatibility issues. + +### Unsupported Elasticsearch version + +**Error message:** "Support for Elasticsearch versions after their end-of-life (currently versions < 7.16) was removed. Using unsupported version of Elasticsearch may lead to unexpected and incorrect results." + +**Cause:** The Elasticsearch version is no longer supported by the Grafana data source. + +**Solution:** + +1. Upgrade Elasticsearch to a supported version (7.17+, 8.x, or 9.x). +1. Refer to [Elastic Product End of Life Dates](https://www.elastic.co/support/eol) for version support information. +1. Note that queries may still work, but Grafana does not guarantee functionality for unsupported versions. + +## Other common issues + +The following issues don't produce specific error messages but are commonly encountered. + +### Empty query results + +**Cause:** The query returns no data. + +**Solution:** + +1. Verify the time range includes data in your index. +1. Check the Lucene query syntax for errors. +1. Test the query directly in Elasticsearch using the `_search` API. +1. Ensure the index contains documents matching your query filters. + +### Slow query performance + +**Cause:** Queries take a long time to execute. + +**Solution:** + +1. Reduce the time range of your query. +1. Add more specific filters to limit the data scanned. +1. Increase the date histogram interval. +1. Check Elasticsearch cluster performance and resource utilization. +1. Consider using index aliases or data streams for better query routing. + +### CORS errors in browser console + +**Cause:** Cross-Origin Resource Sharing (CORS) is blocking requests from the browser to Elasticsearch. + +**Solution:** + +1. Use Server (proxy) access mode instead of Browser access mode in the data source configuration. +1. If Browser access is required, configure CORS settings in Elasticsearch: + +```yaml +http.cors.enabled: true +http.cors.allow-origin: '' +http.cors.allow-headers: 'Authorization, Content-Type' +http.cors.allow-credentials: true +``` + +{{< admonition type="note" >}} +Server (proxy) access mode is recommended for security and reliability. +{{< /admonition >}} + +## Get additional help + +If you continue to experience issues after following this troubleshooting guide: + +1. Check the [Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) for API-specific guidance. +1. Review the [Grafana community forums](https://community.grafana.com/) for similar issues. +1. Contact Grafana Support if you have an Enterprise license. From c35642b04dd589925ffe68c2d66ca086871dd0cb Mon Sep 17 00:00:00 2001 From: Ezequiel Victorero Date: Thu, 11 Dec 2025 16:40:23 -0300 Subject: [PATCH 036/297] Chore: Bump nodemailer with forced resolution (#115172) --- package.json | 3 ++- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8be20ab399f..8b410b35085 100644 --- a/package.json +++ b/package.json @@ -459,7 +459,8 @@ "gitconfiglocal": "2.1.0", "tmp@npm:^0.0.33": "~0.2.1", "js-yaml@npm:4.1.0": "^4.1.0", - "js-yaml@npm:=4.1.0": "^4.1.0" + "js-yaml@npm:=4.1.0": "^4.1.0", + "nodemailer": "7.0.7" }, "workspaces": { "packages": [ diff --git a/yarn.lock b/yarn.lock index 91cbadc61e4..710279ad3d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25168,10 +25168,10 @@ __metadata: languageName: node linkType: hard -"nodemailer@npm:6.9.13": - version: 6.9.13 - resolution: "nodemailer@npm:6.9.13" - checksum: 10/efbc6fc415ec1e1dc1b91530920b0bcfc648183003c3d79718cd54fc2efef4b7dd1917ddd3853ab127e4b5ebd7353903b5859f0ac3ccea487374b076d41ac8b8 +"nodemailer@npm:7.0.7": + version: 7.0.7 + resolution: "nodemailer@npm:7.0.7" + checksum: 10/903d4e0a8320c0e4a2bede6737a9b4996048ddc2e010befc406c8953dcec96ef0e2c17e8b7639654e8bf46844cf7d26f017d8bf9fd629588637b699e09547222 languageName: node linkType: hard From 652b4f2fab422288314bb7595d114ff38415ae19 Mon Sep 17 00:00:00 2001 From: Andres Torres Date: Thu, 11 Dec 2025 15:12:25 -0500 Subject: [PATCH 037/297] fix(setting): Add default scheme to handle k8s api errors (#115177) --- pkg/services/setting/service.go | 6 ++- pkg/services/setting/service_test.go | 76 ++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/pkg/services/setting/service.go b/pkg/services/setting/service.go index 6b49abcb24f..0a249ef65ac 100644 --- a/pkg/services/setting/service.go +++ b/pkg/services/setting/service.go @@ -13,6 +13,7 @@ import ( "go.opentelemetry.io/otel/trace" "gopkg.in/ini.v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apiserver/pkg/endpoints/request" @@ -397,6 +398,9 @@ func getRestClient(config Config, log logging.Logger) (*rest.RESTClient, error) burst = config.Burst } + // Add a default scheme to handle K8s API error responses + scheme := runtime.NewScheme() + restConfig := &rest.Config{ Host: config.URL, TLSClientConfig: config.TLSClientConfig, @@ -407,7 +411,7 @@ func getRestClient(config Config, log logging.Logger) (*rest.RESTClient, error) APIPath: "/apis", ContentConfig: rest.ContentConfig{ GroupVersion: &settingGroupVersion, - NegotiatedSerializer: serializer.NewCodecFactory(nil).WithoutConversion(), + NegotiatedSerializer: serializer.NewCodecFactory(scheme).WithoutConversion(), }, } diff --git a/pkg/services/setting/service_test.go b/pkg/services/setting/service_test.go index 5d9a2565551..9b007fb1dc9 100644 --- a/pkg/services/setting/service_test.go +++ b/pkg/services/setting/service_test.go @@ -148,6 +148,82 @@ func TestRemoteSettingService_List(t *testing.T) { require.Error(t, err) assert.Nil(t, result) }) + + t.Run("should handle API errors", func(t *testing.T) { + statusResponse := `{ + "apiVersion": "v1", + "kind": "Status", + "metadata": {}, + "status": "Failure", + "message": "settings.setting.grafana.app \"test\" not found", + "reason": "NotFound", + "details": { + "name": "test", + "group": "setting.grafana.app", + "kind": "settings" + }, + "code": 404 + }` + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(statusResponse)) + })) + defer server.Close() + + client := newTestClient(t, server.URL, 500) + ctx := request.WithNamespace(context.Background(), "test-namespace") + + result, err := client.List(ctx, metav1.LabelSelector{}) + + require.Error(t, err) + assert.Nil(t, result) + assert.Contains(t, err.Error(), "could not find the requested resource") + }) + + t.Run("should handle 500 internal server error", func(t *testing.T) { + statusResponse := `{ + "apiVersion": "v1", + "kind": "Status", + "metadata": {}, + "status": "Failure", + "message": "Internal error occurred: database connection failed", + "reason": "InternalError", + "code": 500 + }` + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(statusResponse)) + })) + defer server.Close() + + client := newTestClient(t, server.URL, 500) + ctx := request.WithNamespace(context.Background(), "test-namespace") + + result, err := client.List(ctx, metav1.LabelSelector{}) + + require.Error(t, err) + assert.Nil(t, result) + assert.Contains(t, err.Error(), "error on the server") + }) + + t.Run("should handle connection errors", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + serverURL := server.URL + server.Close() + + client := newTestClient(t, serverURL, 500) + ctx := request.WithNamespace(context.Background(), "test-namespace") + + result, err := client.List(ctx, metav1.LabelSelector{}) + + require.Error(t, err) + assert.Nil(t, result) + assert.Contains(t, err.Error(), "connection refused") + }) } func TestParseSettingList(t *testing.T) { From e8039d1c3dd2eaa6568e6a589e4617b0cc22c8e2 Mon Sep 17 00:00:00 2001 From: Eric Hilse Date: Thu, 11 Dec 2025 13:28:30 -0700 Subject: [PATCH 038/297] fix(topbar): remove minWidth property for better layout handling (#115166) --- public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx b/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx index 6542e457f2c..d5179570ef3 100644 --- a/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx +++ b/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx @@ -93,7 +93,6 @@ export const SingleTopBar = memo(function SingleTopBar({ justifyContent={'flex-end'} flex={1} data-testid={!showToolbarLevel ? Components.NavToolbar.container : undefined} - minWidth={{ xs: 'unset', lg: 0 }} > From 1611489b84412e3ef575bcd6889031e57b977f7d Mon Sep 17 00:00:00 2001 From: Jack Baldry Date: Thu, 11 Dec 2025 21:40:35 +0000 Subject: [PATCH 039/297] Fix path to generation and source content (#115095) Signed-off-by: Jack Baldry --- docs/Makefile | 4 +-- .../transform-data/index.md | 3 +- scripts/docs/generate-transformations.ts | 29 ++++++++++--------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 7bdbe026293..8202b11f826 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -7,8 +7,8 @@ MAKEFLAGS += --no-builtin-rule include docs.mk -.PHONY: sources/panels-visualizations/query-transform-data/transform-data/index.md -sources/panels-visualizations/query-transform-data/transform-data/index.md: ## Generate the Transform Data page source. +.PHONY: sources/visualizations/panels-visualizations/query-transform-data/transform-data/index.md +sources/visualizations/panels-visualizations/query-transform-data/transform-data/index.md: ## Generate the Transform Data page source. cd $(CURDIR)/.. && \ npx tsx ./scripts/docs/generate-transformations.ts && \ npx prettier -w $(CURDIR)/$@ diff --git a/docs/sources/visualizations/panels-visualizations/query-transform-data/transform-data/index.md b/docs/sources/visualizations/panels-visualizations/query-transform-data/transform-data/index.md index b6b3499b4d7..2a13836d246 100644 --- a/docs/sources/visualizations/panels-visualizations/query-transform-data/transform-data/index.md +++ b/docs/sources/visualizations/panels-visualizations/query-transform-data/transform-data/index.md @@ -12,12 +12,13 @@ comments: | To build this Markdown, do the following: $ cd /docs (from the root of the repository) - $ make sources/panels-visualizations/query-transform-data/transform-data/index.md + $ make sources/visualizations/panels-visualizations/query-transform-data/transform-data/index.md $ make docs Browse to http://localhost:3003/docs/grafana/latest/panels-visualizations/query-transform-data/transform-data/ Refer to ./docs/README.md "Content guidelines" for more information about editing and building these docs. + aliases: - ../../../panels/transform-data/ # /docs/grafana/next/panels/transform-data/ - ../../../panels/transform-data/about-transformation/ # /docs/grafana/next/panels/transform-data/about-transformation/ diff --git a/scripts/docs/generate-transformations.ts b/scripts/docs/generate-transformations.ts index f6912c9a660..68eec4fa716 100644 --- a/scripts/docs/generate-transformations.ts +++ b/scripts/docs/generate-transformations.ts @@ -20,7 +20,7 @@ export const readMeContent = ` To build this Markdown, do the following: $ cd /docs (from the root of the repository) - $ make sources/panels-visualizations/query-transform-data/transform-data/index.md + $ make sources/visualizations/panels-visualizations/query-transform-data/transform-data/index.md $ make docs Browse to http://localhost:3003/docs/grafana/latest/panels-visualizations/query-transform-data/transform-data/ @@ -34,19 +34,20 @@ comments: | ${readMeContent} aliases: - - ../../panels/reference-transformation-functions/ - - ../../panels/transform-data/ - - ../../panels/transform-data/about-transformation/ - - ../../panels/transform-data/add-transformation-to-data/ - - ../../panels/transform-data/apply-transformation-to-data/ - - ../../panels/transform-data/debug-transformation/ - - ../../panels/transform-data/delete-transformation/ - - ../../panels/transform-data/transformation-functions/ - - ../../panels/transformations/ - - ../../panels/transformations/apply-transformations/ - - ../../panels/transformations/config-from-query/ - - ../../panels/transformations/rows-to-fields/ - - ../../panels/transformations/types-options/ + - ../../../panels/transform-data/ # /docs/grafana/next/panels/transform-data/ + - ../../../panels/transform-data/about-transformation/ # /docs/grafana/next/panels/transform-data/about-transformation/ + - ../../../panels/transform-data/add-transformation-to-data/ # /docs/grafana/next/panels/transform-data/add-transformation-to-data/ + - ../../../panels/transform-data/apply-transformation-to-data/ # /docs/grafana/next/panels/transform-data/apply-transformation-to-data/ + - ../../../panels/transform-data/debug-transformation/ # /docs/grafana/next/panels/transform-data/debug-transformation/ + - ../../../panels/transform-data/delete-transformation/ # /docs/grafana/next/panels/transform-data/delete-transformation/ + - ../../../panels/transform-data/transformation-functions/ # /docs/grafana/next/panels/transform-data/transformation-functions/ + - ../../../panels/transformations/ # /docs/grafana/next/panels/transformations/ + - ../../../panels/transformations/apply-transformations/ # /docs/grafana/next/panels/transformations/apply-transformations/ + - ../../../panels/transformations/config-from-query/ # /docs/grafana/next/panels/transformations/config-from-query/ + - ../../../panels/transformations/rows-to-fields/ # /docs/grafana/next/panels/transformations/rows-to-fields/ + - ../../../panels/transformations/types-options/ # /docs/grafana/next/panels/transformations/types-options/ + - ../../../panels/reference-transformation-functions/ # /docs/grafana/next/panels/reference-transformation-functions/ + - ../../../panels-visualizations/query-transform-data/transform-data/ # /docs/grafana/next/panels-visualizations/query-transform-data/transform-data/ labels: products: - cloud From 0385a7a4a495f276cde1ab42e9654c431cdf65aa Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Thu, 11 Dec 2025 15:54:06 -0700 Subject: [PATCH 040/297] Dashboard Import: disable importing V2 dashboards when dashboardNewLayouts is disabled (#114188) * Disable importing v2 dashboards when dynamic dashboards are disabled * clean up * Update error messaging --- .../manage-dashboards/DashboardImportPage.tsx | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/public/app/features/manage-dashboards/DashboardImportPage.tsx b/public/app/features/manage-dashboards/DashboardImportPage.tsx index fd286a33f2f..b640035c2de 100644 --- a/public/app/features/manage-dashboards/DashboardImportPage.tsx +++ b/public/app/features/manage-dashboards/DashboardImportPage.tsx @@ -94,17 +94,14 @@ class UnthemedDashboardImport extends PureComponent { const json = JSON.parse(String(result)); if (json.spec?.elements) { - dispatch(importDashboardV2Json(json.spec)); - return; + return dispatch(importDashboardV2Json(json.spec)); } else if (json.elements) { - dispatch(importDashboardV2Json(json)); - return; + return dispatch(importDashboardV2Json(json)); } // check if it's a v1 resource format if (json.spec) { - this.props.importDashboardJson(json.spec); - return; + return this.props.importDashboardJson(json.spec); } this.props.importDashboardJson(json); @@ -123,20 +120,26 @@ class UnthemedDashboardImport extends PureComponent { const dashboard = JSON.parse(formData.dashboardJson); + if ((dashboard.spec?.elements || dashboard.elements) && !config.featureToggles.dashboardNewLayouts) { + return appEvents.emit(AppEvents.alertError, [ + 'Import failed', + 'Dashboard using new layout cannot be imported because the feature is not enabled', + ]); + } + // check if it's a v2 resource format if (dashboard.spec?.elements) { - dispatch(importDashboardV2Json(dashboard.spec)); - return; - // check if it's just a v2 spec - } else if (dashboard.elements) { - dispatch(importDashboardV2Json(dashboard)); - return; + return dispatch(importDashboardV2Json(dashboard.spec)); + } + + // check if it's just a v2 spec + if (dashboard.elements) { + return dispatch(importDashboardV2Json(dashboard)); } // check if it's a v1 resource format if (dashboard.spec) { - this.props.importDashboardJson(dashboard.spec); - return; + return this.props.importDashboardJson(dashboard.spec); } this.props.importDashboardJson(dashboard); From b407f0062d07ba7436d5bacb52ecc6d88612861f Mon Sep 17 00:00:00 2001 From: Steve Simpson Date: Thu, 11 Dec 2025 23:34:37 +0000 Subject: [PATCH 041/297] Alerting: Add an authorizer to the historian app (#115188) historian: add an authorizer Co-authored-by: Charandas Batra --- pkg/registry/apps/alerting/historian/register.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/registry/apps/alerting/historian/register.go b/pkg/registry/apps/alerting/historian/register.go index 68830dcd0ef..725cb2fae8d 100644 --- a/pkg/registry/apps/alerting/historian/register.go +++ b/pkg/registry/apps/alerting/historian/register.go @@ -1,9 +1,12 @@ package historian import ( + "context" + "github.com/grafana/grafana-app-sdk/app" appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" "github.com/grafana/grafana-app-sdk/simple" + "k8s.io/apiserver/pkg/authorization/authorizer" restclient "k8s.io/client-go/rest" "github.com/grafana/grafana/apps/alerting/historian/pkg/apis" @@ -23,6 +26,14 @@ type AlertingHistorianAppInstaller struct { appsdkapiserver.AppInstaller } +func (a *AlertingHistorianAppInstaller) GetAuthorizer() authorizer.Authorizer { + return authorizer.AuthorizerFunc( + func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { + return authorizer.DecisionAllow, "", nil + }, + ) +} + func RegisterAppInstaller( cfg *setting.Cfg, ng *ngalert.AlertNG, From c3224411c0c04f8d6965aa33b63dc3ff4b548c25 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 12 Dec 2025 07:45:04 +0100 Subject: [PATCH 042/297] NPM: Use env var for OIDC token auth instead of direct npmrc (#115153) * use env var * ignore spellcheck --- scripts/publish-npm-packages.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/publish-npm-packages.sh b/scripts/publish-npm-packages.sh index 6ee770ee840..3131c791d36 100755 --- a/scripts/publish-npm-packages.sh +++ b/scripts/publish-npm-packages.sh @@ -100,8 +100,11 @@ if (( CHANGES_COUNT > 0 )); then if [ -n "$NPM_AUTH_TOKEN" ]; then # Mask the token so it won't appear in logs echo "::add-mask::$NPM_AUTH_TOKEN" - echo "Configuring npm auth token in ~/.npmrc" - echo "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}" >> ~/.npmrc + echo "Configuring npm auth via NPM_TOKEN env var" + export NPM_TOKEN="$NPM_AUTH_TOKEN" + # Reference the env var in npmrc (single quotes intentional - npm expands it at runtime) + # shellcheck disable=SC2016 + echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' >> ~/.npmrc else echo "Warning: No token in response, dist-tag operation may fail" fi From 35c214249f11cad9425c03a7a68615977b9d1205 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 12 Dec 2025 08:59:10 +0100 Subject: [PATCH 043/297] E2E Selectors: Fix comment typo (#115197) fix typo --- packages/grafana-e2e-selectors/src/selectors/components.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index d6e34db61ca..f1f2ce08642 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -3,7 +3,7 @@ // (a
)} - {!hideLinksControls && !editPanel && }
{!hideVariableControls && ( <> @@ -179,6 +178,7 @@ function DashboardControlsRenderer({ model }: SceneComponentProps )} + {!hideLinksControls && !editPanel && } {!hideDashboardControls && hasDashboardControls && } {editPanel && } {showDebugger && } diff --git a/public/app/features/dashboard-scene/scene/DashboardLinkRenderer.tsx b/public/app/features/dashboard-scene/scene/DashboardLinkRenderer.tsx index e7bc346642b..d2571314782 100644 --- a/public/app/features/dashboard-scene/scene/DashboardLinkRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardLinkRenderer.tsx @@ -64,7 +64,6 @@ function getStyles(theme: GrafanaTheme2) { alignItems: 'center', verticalAlign: 'middle', marginBottom: theme.spacing(1), - marginRight: theme.spacing(1), }), }; } diff --git a/public/app/features/dashboard-scene/scene/DashboardLinksControls.tsx b/public/app/features/dashboard-scene/scene/DashboardLinksControls.tsx index 11639554e5d..46fcf64d121 100644 --- a/public/app/features/dashboard-scene/scene/DashboardLinksControls.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardLinksControls.tsx @@ -36,13 +36,9 @@ export function DashboardLinksControls({ links, dashboard }: Props) { function getStyles(theme: GrafanaTheme2) { return { linksContainer: css({ - display: 'flex', - flexWrap: 'wrap', + display: 'inline-flex', gap: theme.spacing(1), - maxWidth: '100%', - minWidth: 0, - order: 1, - flex: '1 1 0%', + marginRight: theme.spacing(1), }), }; } diff --git a/public/app/features/dashboard-scene/scene/VariableControls.tsx b/public/app/features/dashboard-scene/scene/VariableControls.tsx index 1061938e5e6..03d23712c7b 100644 --- a/public/app/features/dashboard-scene/scene/VariableControls.tsx +++ b/public/app/features/dashboard-scene/scene/VariableControls.tsx @@ -19,7 +19,6 @@ import { AddVariableButton } from './VariableControlsAddButton'; export function VariableControls({ dashboard }: { dashboard: DashboardScene }) { const { variables } = sceneGraph.getVariables(dashboard)!.useState(); - const styles = useStyles2(getStyles); return ( <> @@ -28,11 +27,7 @@ export function VariableControls({ dashboard }: { dashboard: DashboardScene }) { .map((variable) => ( ))} - {config.featureToggles.dashboardNewLayouts ? ( -
- -
- ) : null} + {config.featureToggles.dashboardNewLayouts ? : null} ); } @@ -211,11 +206,4 @@ const getStyles = (theme: GrafanaTheme2) => ({ display: 'flex', alignItems: 'center', }), - addButton: css({ - display: 'inline-flex', - alignItems: 'center', - verticalAlign: 'middle', - marginBottom: theme.spacing(1), - marginRight: theme.spacing(1), - }), }); diff --git a/public/app/features/dashboard-scene/scene/VariableControlsAddButton.tsx b/public/app/features/dashboard-scene/scene/VariableControlsAddButton.tsx index 8bdabd397d1..75d48144146 100644 --- a/public/app/features/dashboard-scene/scene/VariableControlsAddButton.tsx +++ b/public/app/features/dashboard-scene/scene/VariableControlsAddButton.tsx @@ -1,7 +1,9 @@ +import { css } from '@emotion/css'; import { PointerEventHandler, useCallback } from 'react'; +import { GrafanaTheme2 } from '@grafana/data'; import { Trans } from '@grafana/i18n'; -import { Button } from '@grafana/ui'; +import { Button, useStyles2 } from '@grafana/ui'; import { openAddVariablePane } from '../settings/variables/VariableAddEditableElement'; import { DashboardInteractions } from '../utils/interactions'; @@ -9,6 +11,7 @@ import { DashboardInteractions } from '../utils/interactions'; import { DashboardScene } from './DashboardScene'; export function AddVariableButton({ dashboard }: { dashboard: DashboardScene }) { + const styles = useStyles2(getStyles); const { editview, editPanel, isEditing, viewPanel } = dashboard.useState(); const handlePointerDown: PointerEventHandler = useCallback( @@ -30,10 +33,22 @@ export function AddVariableButton({ dashboard }: { dashboard: DashboardScene }) } return ( -
- +
+
+ +
); } + +const getStyles = (theme: GrafanaTheme2) => ({ + addButton: css({ + display: 'inline-flex', + alignItems: 'center', + verticalAlign: 'middle', + marginBottom: theme.spacing(1), + marginRight: theme.spacing(1), + }), +}); diff --git a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx index 43d1dda185f..ae5cffb9b31 100644 --- a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx +++ b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx @@ -182,7 +182,6 @@ function getStyles(theme: GrafanaTheme2) { alignItems: 'center', verticalAlign: 'middle', marginBottom: theme.spacing(1), - marginRight: theme.spacing(1), }), }; } From 7805e18368799443543b9008affd21fec8831c1a Mon Sep 17 00:00:00 2001 From: Paul Marbach Date: Fri, 12 Dec 2025 07:56:31 -0500 Subject: [PATCH 060/297] Sparkline: Export a class component for now (#115189) --- .../src/components/Sparkline/Sparkline.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx b/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx index 68b1a5a832f..c18b235e757 100644 --- a/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx +++ b/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx @@ -16,7 +16,7 @@ export interface SparklineProps extends Themeable2 { sparkline: FieldSparkline; } -export const Sparkline: React.FC = memo((props) => { +const SparklineFn: React.FC = memo((props) => { const { sparkline, config: fieldConfig, theme, width, height } = props; const { frame: alignedDataFrame, warning } = prepareSeries(sparkline, fieldConfig); @@ -30,4 +30,14 @@ export const Sparkline: React.FC = memo((props) => { return ; }); -Sparkline.displayName = 'Sparkline'; +SparklineFn.displayName = 'Sparkline'; + +// we converted to function component above, but some apps extend Sparkline, so we need +// to keep exporting a class component until those apps are all rolled out. +// see https://github.com/grafana/app-observability-plugin/pull/2079 +// eslint-disable-next-line react-prefer-function-component/react-prefer-function-component +export class Sparkline extends React.PureComponent { + render() { + return ; + } +} From e525b529a861614aa5f4870272f8a297bbce8b2c Mon Sep 17 00:00:00 2001 From: Charandas <542168+charandas@users.noreply.github.com> Date: Fri, 12 Dec 2025 05:01:03 -0800 Subject: [PATCH 061/297] fix: Add panic for nil authorizer in installer (#115186) --- pkg/registry/apis/service/register.go | 1 + pkg/registry/apps/correlations/register.go | 7 ++++ pkg/registry/apps/playlist/register.go | 8 +++++ pkg/registry/apps/quotas/register.go | 7 ++++ .../apiserver/appinstaller/installer.go | 2 ++ .../apiserver/appinstaller/installer_test.go | 33 ++++++++++++++----- .../apiserver/auth/authorizer/authorizer.go | 8 ++--- .../apiserver/auth/authorizer/role.go | 1 + 8 files changed, 55 insertions(+), 12 deletions(-) diff --git a/pkg/registry/apis/service/register.go b/pkg/registry/apis/service/register.go index 1002bff5bf6..db909b08777 100644 --- a/pkg/registry/apis/service/register.go +++ b/pkg/registry/apis/service/register.go @@ -38,6 +38,7 @@ func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration bui } func (b *ServiceAPIBuilder) GetAuthorizer() authorizer.Authorizer { + //nolint:staticcheck // not yet migrated to Resource Authorizer return roleauthorizer.NewRoleAuthorizer() } diff --git a/pkg/registry/apps/correlations/register.go b/pkg/registry/apps/correlations/register.go index 757af68a8ce..2a3b1f0bde5 100644 --- a/pkg/registry/apps/correlations/register.go +++ b/pkg/registry/apps/correlations/register.go @@ -5,6 +5,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/authorization/authorizer" restclient "k8s.io/client-go/rest" "github.com/grafana/grafana-app-sdk/app" @@ -16,6 +17,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/apiserver/appinstaller" + roleauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/correlations" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -60,6 +62,11 @@ func RegisterAppInstaller( return installer, nil } +func (a *AppInstaller) GetAuthorizer() authorizer.Authorizer { + //nolint:staticcheck // not yet migrated to Resource Authorizer + return roleauthorizer.NewRoleAuthorizer() +} + func (a *AppInstaller) GetLegacyStorage(requested schema.GroupVersionResource) rest.Storage { kind := correlationsV0.CorrelationKind() gvr := schema.GroupVersionResource{ diff --git a/pkg/registry/apps/playlist/register.go b/pkg/registry/apps/playlist/register.go index 52bbc0210f9..336270098e4 100644 --- a/pkg/registry/apps/playlist/register.go +++ b/pkg/registry/apps/playlist/register.go @@ -6,17 +6,20 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/authorization/authorizer" restclient "k8s.io/client-go/rest" "github.com/grafana/grafana-app-sdk/app" appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" "github.com/grafana/grafana-app-sdk/simple" + "github.com/grafana/grafana/apps/playlist/pkg/apis" playlistv0alpha1 "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1" playlistapp "github.com/grafana/grafana/apps/playlist/pkg/app" "github.com/grafana/grafana/pkg/apimachinery/utils" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/apiserver/appinstaller" + roleauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/featuremgmt" playlistsvc "github.com/grafana/grafana/pkg/services/playlist" @@ -63,6 +66,11 @@ func RegisterAppInstaller( return installer, nil } +func (p *PlaylistAppInstaller) GetAuthorizer() authorizer.Authorizer { + //nolint:staticcheck // not yet migrated to Resource Authorizer + return roleauthorizer.NewRoleAuthorizer() +} + // GetLegacyStorage returns the legacy storage for the playlist app. func (p *PlaylistAppInstaller) GetLegacyStorage(requested schema.GroupVersionResource) grafanarest.Storage { gvr := playlistv0alpha1.PlaylistKind().GroupVersionResource() diff --git a/pkg/registry/apps/quotas/register.go b/pkg/registry/apps/quotas/register.go index b81d4fef5cf..c6b33bc4d00 100644 --- a/pkg/registry/apps/quotas/register.go +++ b/pkg/registry/apps/quotas/register.go @@ -3,12 +3,14 @@ package quotas import ( "github.com/grafana/grafana/apps/quotas/pkg/apis" "github.com/grafana/grafana/pkg/storage/unified/resource" + "k8s.io/apiserver/pkg/authorization/authorizer" restclient "k8s.io/client-go/rest" "github.com/grafana/grafana-app-sdk/app" appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" "github.com/grafana/grafana-app-sdk/simple" quotasapp "github.com/grafana/grafana/apps/quotas/pkg/app" + roleauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" ) @@ -22,6 +24,11 @@ type QuotasAppInstaller struct { cfg *setting.Cfg } +func (a *QuotasAppInstaller) GetAuthorizer() authorizer.Authorizer { + //nolint:staticcheck // not yet migrated to Resource Authorizer + return roleauthorizer.NewRoleAuthorizer() +} + func RegisterAppInstaller( cfg *setting.Cfg, features featuremgmt.FeatureToggles, diff --git a/pkg/services/apiserver/appinstaller/installer.go b/pkg/services/apiserver/appinstaller/installer.go index c7b926bd5f3..d282b42f547 100644 --- a/pkg/services/apiserver/appinstaller/installer.go +++ b/pkg/services/apiserver/appinstaller/installer.go @@ -114,6 +114,8 @@ func RegisterAuthorizers( registrar.Register(gv, authorizer) logger.Debug("Registered authorizer", "group", gv.Group, "version", gv.Version, "app") } + } else { + panic("authorizer cannot be nil for api group: " + installer.GroupVersions()[0].Group) } } } diff --git a/pkg/services/apiserver/appinstaller/installer_test.go b/pkg/services/apiserver/appinstaller/installer_test.go index 89c4bfa0164..53c2d2dbedf 100644 --- a/pkg/services/apiserver/appinstaller/installer_test.go +++ b/pkg/services/apiserver/appinstaller/installer_test.go @@ -15,6 +15,7 @@ func TestRegisterAuthorizers(t *testing.T) { name string appInstallers []appsdkapiserver.AppInstaller expectedRegisters int + expectedPanic bool }{ { name: "empty installers list", @@ -30,7 +31,7 @@ func TestRegisterAuthorizers(t *testing.T) { }, }, }, - expectedRegisters: 0, + expectedPanic: true, }, { name: "single installer with authorizer provider", @@ -46,6 +47,20 @@ func TestRegisterAuthorizers(t *testing.T) { }, expectedRegisters: 1, }, + { + name: "single installer with invalid authorizer provider", + appInstallers: []appsdkapiserver.AppInstaller{ + &mockAppInstallerWithAuth{ + mockAppInstaller: &mockAppInstaller{ + groupVersions: []schema.GroupVersion{ + {Group: "test.example.com", Version: "v1"}, + }, + }, + mockAuthorizer: nil, + }, + }, + expectedPanic: true, + }, { name: "installer with multiple group versions", appInstallers: []appsdkapiserver.AppInstaller{ @@ -63,7 +78,7 @@ func TestRegisterAuthorizers(t *testing.T) { expectedRegisters: 3, }, { - name: "multiple installers with mixed authorizer support", + name: "multiple installers with authorizer support", appInstallers: []appsdkapiserver.AppInstaller{ &mockAppInstallerWithAuth{ mockAppInstaller: &mockAppInstaller{ @@ -73,11 +88,6 @@ func TestRegisterAuthorizers(t *testing.T) { }, mockAuthorizer: &mockAuthorizer{}, }, - &mockAppInstaller{ - groupVersions: []schema.GroupVersion{ - {Group: "other.example.com", Version: "v1"}, - }, - }, &mockAppInstallerWithAuth{ mockAppInstaller: &mockAppInstaller{ groupVersions: []schema.GroupVersion{ @@ -88,7 +98,7 @@ func TestRegisterAuthorizers(t *testing.T) { mockAuthorizer: &mockAuthorizer{}, }, }, - expectedRegisters: 3, // 1 from first installer + 2 from third installer + expectedRegisters: 3, // 1 from first installer + 2 from second installer }, } @@ -96,6 +106,13 @@ func TestRegisterAuthorizers(t *testing.T) { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() registrar := &mockAuthorizerRegistrar{} + if tt.expectedPanic { + defer func() { + if r := recover(); r == nil { + t.Errorf("%s case did not panic as expected", t.Name()) + } + }() + } RegisterAuthorizers(ctx, tt.appInstallers, registrar) require.Equal(t, tt.expectedRegisters, len(registrar.registrations)) }) diff --git a/pkg/services/apiserver/auth/authorizer/authorizer.go b/pkg/services/apiserver/auth/authorizer/authorizer.go index f58c1d14bf8..dab8167deb0 100644 --- a/pkg/services/apiserver/auth/authorizer/authorizer.go +++ b/pkg/services/apiserver/auth/authorizer/authorizer.go @@ -38,12 +38,12 @@ func NewGrafanaBuiltInSTAuthorizer(cfg *setting.Cfg) *GrafanaAuthorizer { // Individual services may have explicit implementations apis := make(map[string]authorizer.Authorizer) + // The apiVersion flavors will run first and can return early when FGAC has appropriate rules authorizers = append(authorizers, &authorizerForAPI{apis}) - // org role is last -- and will return allow for verbs that match expectations - // The apiVersion flavors will run first and can return early when FGAC has appropriate rules - // NOTE: role authorizer is now used by some api groups as their specific authorizer - // but there are still some apis not directly registered in the embedded delegate that benefit from including it here + // org role authorizer is last -- and will return allow for verbs that match expectations + // it is only helpful here for remote APIs in some cloud use-cases. + //nolint:staticcheck // remove once build handler chains are untangled between local and remote APIs handling authorizers = append(authorizers, NewRoleAuthorizer()) return &GrafanaAuthorizer{ apis: apis, diff --git a/pkg/services/apiserver/auth/authorizer/role.go b/pkg/services/apiserver/auth/authorizer/role.go index 23164dbf556..e8e70dd01c8 100644 --- a/pkg/services/apiserver/auth/authorizer/role.go +++ b/pkg/services/apiserver/auth/authorizer/role.go @@ -19,6 +19,7 @@ var orgRoleNoneAsViewerAPIGroups = []string{ type roleAuthorizer struct{} +// Deprecated: NewRoleAuthorizer exists for apps that were launched with simplistic authorization requirements. Consider using NewResourceAuthorizer instead. func NewRoleAuthorizer() *roleAuthorizer { return &roleAuthorizer{} } From b2dd095bd860cfbc9187e0fdf91cae18206044e4 Mon Sep 17 00:00:00 2001 From: Will Assis <35489495+gassiss@users.noreply.github.com> Date: Fri, 12 Dec 2025 08:09:51 -0500 Subject: [PATCH 062/297] Unified-storage: sql backend key path backfill (#115033) * unified-storage: add migration to backfill key_path in resource_history --- .../unified/sql/db/migrations/resource_mig.go | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/pkg/storage/unified/sql/db/migrations/resource_mig.go b/pkg/storage/unified/sql/db/migrations/resource_mig.go index fbbfe32d4a6..170b418a22a 100644 --- a/pkg/storage/unified/sql/db/migrations/resource_mig.go +++ b/pkg/storage/unified/sql/db/migrations/resource_mig.go @@ -2,8 +2,11 @@ package migrations import ( "fmt" + "strings" + "github.com/bwmarrin/snowflake" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/util/xorm" ) func initResourceTables(mg *migrator.Migrator) string { @@ -204,5 +207,142 @@ func initResourceTables(mg *migrator.Migrator) string { Name: "IDX_resource_history_key_path", })) + mg.AddMigration("resource_history key_path backfill", &ResourceHistoryKeyPathBackfillMigration{}) + return marker } + +type ResourceHistoryKeyPathBackfillMigration struct { + migrator.MigrationBase +} + +func (m *ResourceHistoryKeyPathBackfillMigration) SQL(_ migrator.Dialect) string { + return "resource_history key_path backfill code migration" +} + +func (m *ResourceHistoryKeyPathBackfillMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) error { + rows, err := getResourceHistoryRows(sess, mg, resourceHistoryRow{}) + if err != nil { + return err + } + + for len(rows) > 0 { + if err := updateResourceHistoryKeyPath(sess, rows); err != nil { + return err + } + + rows, err = getResourceHistoryRows(sess, mg, rows[len(rows)-1]) + if err != nil { + return err + } + } + + return nil +} + +func updateResourceHistoryKeyPath(sess *xorm.Session, rows []resourceHistoryRow) error { + if len(rows) == 0 { + return nil + } + + updates := []resourceHistoryRow{} + + for _, row := range rows { + if row.KeyPath == "" { + row.KeyPath = parseKeyPath(row) + updates = append(updates, row) + } + } + + if len(updates) == 0 { + return nil + } + + guids := "" + setCases := "CASE" + for _, row := range updates { + guids += fmt.Sprintf("'%s',", row.GUID) + setCases += fmt.Sprintf(" WHEN guid = '%s' THEN '%s'", row.GUID, row.KeyPath) + } + + guids = strings.TrimRight(guids, ",") + setCases += " ELSE key_path END " + + // the query will look like this + // UPDATE resource_history + // SET key_path = CASE + // WHEN guid = '1402de51-669b-4206-8a6c-005a00eee6e3' then 'unified/data/folder.grafana.app/folders/default/cf6lylpvls000c/1998492888241012800~created~' + // WHEN guid = '8842cc56-f22b-45e1-82b1-99759cd443b3' then 'unified/data/dashboard.grafana.app/dashboards/default/adzvfhp/1998492902577144677~created~cf6lylpvls000c' + // ELSE key_path END + // WHERE guid IN ('1402de51-669b-4206-8a6c-005a00eee6e3', '8842cc56-f22b-45e1-82b1-99759cd443b3') + // AND key_path = ''; + sql := fmt.Sprintf(` + UPDATE resource_history + SET key_path = %s + WHERE guid IN (%s) + AND key_path = ''; + `, setCases, guids) + + if _, err := sess.Exec(sql); err != nil { + return err + } + + return nil +} + +func parseKeyPath(row resourceHistoryRow) string { + var action string + switch row.Action { + case 1: + action = "created" + case 2: + action = "updated" + case 3: + action = "deleted" + } + return fmt.Sprintf("unified/data/%s/%s/%s/%s/%d~%s~%s", row.Group, row.Resource, row.Namespace, row.Name, snowflakeFromRv(row.ResourceVersion), action, row.Folder) +} + +func snowflakeFromRv(rv int64) int64 { + return (((rv / 1000) - snowflake.Epoch) << (snowflake.NodeBits + snowflake.StepBits)) + (rv % 1000) +} + +type resourceHistoryRow struct { + GUID string `xorm:"guid"` + Group string `xorm:"group"` + Resource string `xorm:"resource"` + Namespace string `xorm:"namespace"` + Name string `xorm:"name"` + ResourceVersion int64 `xorm:"resource_version"` + Action int64 `xorm:"action"` + Folder string `xorm:"folder"` + KeyPath string `xorm:"key_path"` +} + +func getResourceHistoryRows(sess *xorm.Session, mg *migrator.Migrator, continueRow resourceHistoryRow) ([]resourceHistoryRow, error) { + var rows []resourceHistoryRow + cols := fmt.Sprintf( + "%s, %s, %s, %s, %s, %s, %s, %s, %s", + mg.Dialect.Quote("guid"), + mg.Dialect.Quote("group"), + mg.Dialect.Quote("resource"), + mg.Dialect.Quote("namespace"), + mg.Dialect.Quote("name"), + mg.Dialect.Quote("resource_version"), + mg.Dialect.Quote("action"), + mg.Dialect.Quote("folder"), + mg.Dialect.Quote("key_path")) + sql := fmt.Sprintf(` + SELECT %s + FROM resource_history + WHERE (resource_version > %d OR (resource_version = %d AND guid > '%s')) + AND key_path = '' + ORDER BY resource_version ASC, guid ASC + LIMIT 1000; + `, cols, continueRow.ResourceVersion, continueRow.ResourceVersion, continueRow.GUID) + if err := sess.SQL(sql).Find(&rows); err != nil { + return nil, err + } + + return rows, nil +} From 6512259acc2d56d8f9b2d17b2c2f4cc31b041044 Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Fri, 12 Dec 2025 10:10:05 -0300 Subject: [PATCH 063/297] DashboardLibrary: Restore New dashboard naming (#115184) --- public/app/core/utils/navBarItem-translations.ts | 5 +---- public/app/features/search/tempI18nPhrases.ts | 5 +---- public/locales/en-US/grafana.json | 2 -- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/public/app/core/utils/navBarItem-translations.ts b/public/app/core/utils/navBarItem-translations.ts index 0ac5619e893..0fab359de4e 100644 --- a/public/app/core/utils/navBarItem-translations.ts +++ b/public/app/core/utils/navBarItem-translations.ts @@ -1,5 +1,4 @@ import { t } from '@grafana/i18n'; -import { config } from '@grafana/runtime'; // Maps the ID of the nav item to a translated phrase to later pass to // Because the navigation content is dynamic (defined in the backend), we can not use // the normal inline message definition method. @@ -49,9 +48,7 @@ export function getNavTitle(navId: string | undefined) { case 'dashboards/recently-deleted': return t('nav.recently-deleted.title', 'Recently deleted'); case 'dashboards/new': - return config.featureToggles.dashboardTemplates - ? t('nav.new-dashboard.empty-title', 'Empty dashboard') - : t('nav.new-dashboard.title', 'New dashboard'); + return t('nav.new-dashboard.title', 'New dashboard'); case 'dashboards/folder/new': return t('nav.new-folder.title', 'New folder'); case 'dashboards/import': diff --git a/public/app/features/search/tempI18nPhrases.ts b/public/app/features/search/tempI18nPhrases.ts index 73766e4bff6..926bf24ea73 100644 --- a/public/app/features/search/tempI18nPhrases.ts +++ b/public/app/features/search/tempI18nPhrases.ts @@ -2,7 +2,6 @@ // TODO: remove this when new Browse Dashboards UI is no longer feature flagged import { t } from '@grafana/i18n'; -import { config } from '@grafana/runtime'; export function getSearchPlaceholder(includePanels = false) { return includePanels @@ -11,9 +10,7 @@ export function getSearchPlaceholder(includePanels = false) { } export function getNewDashboardPhrase() { - return config.featureToggles.dashboardTemplates - ? t('search.dashboard-actions.empty-dashboard', 'Empty dashboard') - : t('search.dashboard-actions.new-dashboard', 'New dashboard'); + return t('search.dashboard-actions.new-dashboard', 'New dashboard'); } export function getNewTemplateDashboardPhrase() { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index f0848b1fe3d..6c760e94814 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -10724,7 +10724,6 @@ "title": "New" }, "new-dashboard": { - "empty-title": "Empty dashboard", "title": "New dashboard" }, "new-folder": { @@ -12634,7 +12633,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "Empty dashboard", "import": "Import", "new": "New", "new-dashboard": "New dashboard", From 403f4d41deadd54c5af51b60a28ef3af868771ab Mon Sep 17 00:00:00 2001 From: Matheus Macabu Date: Fri, 12 Dec 2025 15:17:44 +0100 Subject: [PATCH 064/297] APIServer: Add wiring for audit backend and policy rule evaluator (#115212) --- pkg/apiserver/auditing/noop.go | 29 +++++++++++++++++++++++++++++ pkg/registry/apis/wireset.go | 5 +++++ pkg/server/wire_gen.go | 9 +++++++-- pkg/services/apiserver/service.go | 12 ++++++++++++ 4 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 pkg/apiserver/auditing/noop.go diff --git a/pkg/apiserver/auditing/noop.go b/pkg/apiserver/auditing/noop.go new file mode 100644 index 00000000000..5ab8f902c19 --- /dev/null +++ b/pkg/apiserver/auditing/noop.go @@ -0,0 +1,29 @@ +package auditing + +import ( + auditinternal "k8s.io/apiserver/pkg/apis/audit" + "k8s.io/apiserver/pkg/audit" + "k8s.io/apiserver/pkg/authorization/authorizer" +) + +// NoopBackend is a no-op implementation of audit.Backend +type NoopBackend struct{} + +func ProvideNoopBackend() audit.Backend { return &NoopBackend{} } + +func (b *NoopBackend) ProcessEvents(k8sEvents ...*auditinternal.Event) bool { return false } + +func (NoopBackend) Run(stopCh <-chan struct{}) error { return nil } + +func (NoopBackend) Shutdown() {} + +func (NoopBackend) String() string { return "" } + +// NoopPolicyRuleEvaluator is a no-op implementation of audit.PolicyRuleEvaluator +type NoopPolicyRuleEvaluator struct{} + +func ProvideNoopPolicyRuleEvaluator() audit.PolicyRuleEvaluator { return &NoopPolicyRuleEvaluator{} } + +func (NoopPolicyRuleEvaluator) EvaluatePolicyRule(authorizer.Attributes) audit.RequestAuditConfig { + return audit.RequestAuditConfig{Level: auditinternal.LevelNone} +} diff --git a/pkg/registry/apis/wireset.go b/pkg/registry/apis/wireset.go index 12153f812a1..740f2a46cef 100644 --- a/pkg/registry/apis/wireset.go +++ b/pkg/registry/apis/wireset.go @@ -3,6 +3,7 @@ package apiregistry import ( "github.com/google/wire" + "github.com/grafana/grafana/pkg/apiserver/auditing" "github.com/grafana/grafana/pkg/registry/apis/collections" dashboardinternal "github.com/grafana/grafana/pkg/registry/apis/dashboard" "github.com/grafana/grafana/pkg/registry/apis/datasource" @@ -33,6 +34,10 @@ var WireSetExts = wire.NewSet( externalgroupmapping.ProvideNoopTeamGroupsREST, wire.Bind(new(externalgroupmapping.TeamGroupsHandler), new(*externalgroupmapping.NoopTeamGroupsREST)), + + // Auditing Options + auditing.ProvideNoopBackend, + auditing.ProvideNoopPolicyRuleEvaluator, ) var provisioningExtras = wire.NewSet( diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 5782e7d7018..5abda77524a 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/api/avatar" "github.com/grafana/grafana/pkg/api/routing" + "github.com/grafana/grafana/pkg/apiserver/auditing" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/configprovider" "github.com/grafana/grafana/pkg/expr" @@ -831,7 +832,9 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api } v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, appInstaller, shortURLAppInstaller, alertingRulesAppInstaller, correlationsAppInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller, alertingHistorianAppInstaller, quotasAppInstaller) builderMetrics := builder.ProvideBuilderMetrics(registerer) - apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics) + backend := auditing.ProvideNoopBackend() + policyRuleEvaluator := auditing.ProvideNoopPolicyRuleEvaluator() + apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics, backend, policyRuleEvaluator) if err != nil { return nil, err } @@ -1489,7 +1492,9 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac } v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, appInstaller, shortURLAppInstaller, alertingRulesAppInstaller, correlationsAppInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller, alertingHistorianAppInstaller, quotasAppInstaller) builderMetrics := builder.ProvideBuilderMetrics(registerer) - apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics) + backend := auditing.ProvideNoopBackend() + policyRuleEvaluator := auditing.ProvideNoopPolicyRuleEvaluator() + apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics, backend, policyRuleEvaluator) if err != nil { return nil, err } diff --git a/pkg/services/apiserver/service.go b/pkg/services/apiserver/service.go index ac38dee7e3f..5d9e37e649e 100644 --- a/pkg/services/apiserver/service.go +++ b/pkg/services/apiserver/service.go @@ -12,6 +12,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apiserver/pkg/audit" genericapifilters "k8s.io/apiserver/pkg/endpoints/filters" "k8s.io/apiserver/pkg/endpoints/responsewriter" genericapiserver "k8s.io/apiserver/pkg/server" @@ -113,6 +114,9 @@ type service struct { appInstallers []appsdkapiserver.AppInstaller builderMetrics *builder.BuilderMetrics dualWriterMetrics *grafanarest.DualWriterMetrics + + auditBackend audit.Backend + auditPolicyRuleEvaluator audit.PolicyRuleEvaluator } func ProvideService( @@ -137,6 +141,8 @@ func ProvideService( aggregatorRunner aggregatorrunner.AggregatorRunner, appInstallers []appsdkapiserver.AppInstaller, builderMetrics *builder.BuilderMetrics, + auditBackend audit.Backend, + auditPolicyRuleEvaluator audit.PolicyRuleEvaluator, ) (*service, error) { scheme := builder.ProvideScheme() codecs := builder.ProvideCodecFactory(scheme) @@ -167,6 +173,8 @@ func ProvideService( appInstallers: appInstallers, builderMetrics: builderMetrics, dualWriterMetrics: grafanarest.NewDualWriterMetrics(reg), + auditBackend: auditBackend, + auditPolicyRuleEvaluator: auditPolicyRuleEvaluator, } // This will be used when running as a dskit service s.NamedService = services.NewBasicService(s.start, s.running, nil).WithName(modules.GrafanaAPIServer) @@ -355,6 +363,10 @@ func (s *service) start(ctx context.Context) error { appinstaller.BuildOpenAPIDefGetter(s.appInstallers), } + // Auditing Options + serverConfig.AuditBackend = s.auditBackend + serverConfig.AuditPolicyRuleEvaluator = s.auditPolicyRuleEvaluator + // Add OpenAPI specs for each group+version (existing builders) err = builder.SetupConfig( s.scheme, From 6ce672dd00feca5e12b576e9a0b2f045b7d75351 Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Fri, 12 Dec 2025 22:37:43 +0800 Subject: [PATCH 065/297] Chore: Fix mysql query for annotation migration (#115222) fix mysql query for annotation migration --- .../sqlstore/migrations/annotation_mig.go | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pkg/services/sqlstore/migrations/annotation_mig.go b/pkg/services/sqlstore/migrations/annotation_mig.go index 914f73819fc..d1cd33b108f 100644 --- a/pkg/services/sqlstore/migrations/annotation_mig.go +++ b/pkg/services/sqlstore/migrations/annotation_mig.go @@ -261,8 +261,8 @@ func RunDashboardUIDMigrations(sess *xorm.Session, driverName string, logger log logger.Info("Starting batched dashboard_uid migration for annotations (newest first)", "batchSize", batchSize) updateSQL := `UPDATE annotation SET dashboard_uid = (SELECT uid FROM dashboard WHERE dashboard.id = annotation.dashboard_id) - WHERE dashboard_uid IS NULL - AND dashboard_id != 0 + WHERE dashboard_uid IS NULL + AND dashboard_id != 0 AND EXISTS (SELECT 1 FROM dashboard WHERE dashboard.id = annotation.dashboard_id) AND annotation.id IN ( SELECT id FROM annotation @@ -285,19 +285,19 @@ func RunDashboardUIDMigrations(sess *xorm.Session, driverName string, logger log LIMIT $1 )` case MySQL: - updateSQL = `UPDATE annotation - INNER JOIN dashboard ON annotation.dashboard_id = dashboard.id - SET annotation.dashboard_uid = dashboard.uid - WHERE annotation.dashboard_uid IS NULL - AND annotation.dashboard_id != 0 - AND annotation.id IN ( - SELECT id FROM ( - SELECT id FROM annotation - WHERE dashboard_uid IS NULL AND dashboard_id != 0 - ORDER BY id DESC - LIMIT ? - ) AS batch - )` + updateSQL = `UPDATE annotation AS a + JOIN dashboard AS d ON a.dashboard_id = d.id + JOIN ( + SELECT id + FROM annotation + WHERE dashboard_uid IS NULL + AND dashboard_id != 0 + ORDER BY id DESC + LIMIT ? + ) AS batch ON batch.id = a.id + SET a.dashboard_uid = d.uid + WHERE a.dashboard_uid IS NULL + AND a.dashboard_id != 0` } updatedTotal := int64(0) From 46ef9aaa0aad79bdebb8cc9d48037826a2c1a543 Mon Sep 17 00:00:00 2001 From: Johnny Kartheiser <140559259+JohnnyK-Grafana@users.noreply.github.com> Date: Fri, 12 Dec 2025 08:58:10 -0600 Subject: [PATCH 066/297] Alerting docs: Links fix (#115044) * alerting docs: links fix fixes 404 errors * Alerting docs: Fix Slack integration links Fixes Slack links and clarifies the first two steps. * prettier --- .../integrations/configure-alertmanager.md | 6 +++--- .../integrations/configure-jira.md | 16 ++++++++-------- .../integrations/configure-mqtt.md | 8 ++++---- .../integrations/configure-slack.md | 4 ++-- .../integrations/webhook-notifier.md | 6 +++--- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-alertmanager.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-alertmanager.md index 4cdc84ec366..f34a9eeb654 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-alertmanager.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-alertmanager.md @@ -59,9 +59,9 @@ For more details on contact points, including how to test them and enable notifi ## Alertmanager settings -| Option | Description | -| ------ | ---------------------------------------------------------------------------------------------------------------------------------- | -| URL | The Alertmanager URL. This field is [protected](ref:configure-contact-points#protected-fields) from modification in Grafana Cloud. | +| Option | Description | +| ------ | ----------------------------------------------------------------------------------------------------------------- | +| URL | The Alertmanager URL. This field is [protected](ref:configure-contact-points) from modification in Grafana Cloud. | #### Optional settings diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-jira.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-jira.md index 659341405f1..9f68f70ae08 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-jira.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-jira.md @@ -49,14 +49,14 @@ For more details on contact points, including how to test them and enable notifi ### Required Settings -| Key | Description | -| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| URL | The URL of the REST API of your Jira instance. Supported versions: `2` and `3` (e.g., `https://your-domain.atlassian.net/rest/api/3`). This field is [protected](ref:configure-contact-points#protected-fields) from modification in Grafana Cloud. | -| Basic Auth User | Username for authentication. For Jira Cloud, use your email address. | -| Basic Auth Password | Password or personal token. For Jira Cloud, you need to obtain a personal token [here](https://id.atlassian.com/manage-profile/security/api-tokens) and use it as the password. | -| API Token | An alternative to basic authentication, a bearer token is used to authorize the API requests. See [Jira documentation](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html) for more information. | -| Project Key | The project key identifying the project where issues will be created. Project keys are unique identifiers for a project. | -| Issue Type | The type of issue to create (e.g., `Task`, `Bug`, `Incident`). Make sure that you specify a type that is available in your project. | +| Key | Description | +| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| URL | The URL of the REST API of your Jira instance. Supported versions: `2` and `3` (e.g., `https://your-domain.atlassian.net/rest/api/3`). This field is [protected](ref:configure-contact-points) from modification in Grafana Cloud. | +| Basic Auth User | Username for authentication. For Jira Cloud, use your email address. | +| Basic Auth Password | Password or personal token. For Jira Cloud, you need to obtain a personal token [here](https://id.atlassian.com/manage-profile/security/api-tokens) and use it as the password. | +| API Token | An alternative to basic authentication, a bearer token is used to authorize the API requests. See [Jira documentation](https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html) for more information. | +| Project Key | The project key identifying the project where issues will be created. Project keys are unique identifiers for a project. | +| Issue Type | The type of issue to create (e.g., `Task`, `Bug`, `Incident`). Make sure that you specify a type that is available in your project. | ### Optional Settings diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-mqtt.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-mqtt.md index 6f76a574619..3f6a403d27a 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-mqtt.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-mqtt.md @@ -54,10 +54,10 @@ For more details on contact points, including how to test them and enable notifi ### Required Settings -| Option | Description | -| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| Broker URL | The URL of the MQTT broker. This field is [protected](ref:configure-contact-points#protected-fields) from modification in Grafana Cloud. | -| Topic | The topic to which the message will be sent. | +| Option | Description | +| ---------- | ----------------------------------------------------------------------------------------------------------------------- | +| Broker URL | The URL of the MQTT broker. This field is [protected](ref:configure-contact-points) from modification in Grafana Cloud. | +| Topic | The topic to which the message will be sent. | ### Optional Settings diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-slack.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-slack.md index 8f56174ed60..3bda324ec23 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-slack.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-slack.md @@ -51,8 +51,8 @@ You can customize the `title` and `body` of the Slack message using [notificatio If you are using a Slack API Token, complete the following steps. -1. Follow steps 1 and 2 of the [Slack API Quickstart](https://api.slack.com/start/quickstart). -1. Add the [chat:write.public](https://api.slack.com/scopes/chat:write.public) scope to give your app the ability to post in all public channels without joining. +1. Follow step 1 of the [Slack API Quickstart](https://docs.slack.dev/app-management/quickstart-app-settings/#creating) to create the app. +1. Continue onto the second step of the [Slack API Quickstart](https://docs.slack.dev/app-management/quickstart-app-settings/#scopes) and add the [chat:write.public](https://api.slack.com/scopes/chat:write.public) scope as described to give your app the ability to post in all public channels without joining. 1. In OAuth Tokens for Your Workspace, copy the Bot User OAuth Token. 1. Open your Slack workplace. 1. Right click the channel you want to receive notifications in. diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md index 120a45be2a2..d24b54fc568 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md @@ -62,9 +62,9 @@ For more details on contact points, including how to test them and enable notifi ## Webhook settings -| Option | Description | -| ------ | ----------------------------------------------------------------------------------------------------------------------------- | -| URL | The Webhook URL. This field is [protected](ref:configure-contact-points#protected-fields) from modification in Grafana Cloud. | +| Option | Description | +| ------ | ------------------------------------------------------------------------------------------------------------ | +| URL | The Webhook URL. This field is [protected](ref:configure-contact-points) from modification in Grafana Cloud. | #### Optional settings From d4a627c5fc9c5e646675673f65db57b7f4857d83 Mon Sep 17 00:00:00 2001 From: Gonzalo Trigueros Manzanas <242162051+gttrigger@users.noreply.github.com> Date: Fri, 12 Dec 2025 15:59:45 +0100 Subject: [PATCH 067/297] Provisioning: Add resource-level warning support. (#115023) --- .../pkg/apis/provisioning/v0alpha1/jobs.go | 17 +- .../v0alpha1/zz_generated.deepcopy.go | 10 ++ .../v0alpha1/zz_generated.openapi.go | 37 +++- ...enerated.openapi_violation_exceptions.list | 2 + .../v0alpha1/jobresourcesummary.go | 40 +++-- .../provisioning/v0alpha1/jobstatus.go | 11 ++ .../provisioning/v0alpha1/endpoints.gen.ts | 6 +- .../apis/provisioning/jobs/progress.go | 31 +++- .../apis/provisioning/jobs/progress_test.go | 166 ++++++++++++++++++ .../provisioning.grafana.app-v0alpha1.json | 21 ++- 10 files changed, 314 insertions(+), 27 deletions(-) diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go index b96fb1a6d27..2e7700ac4a2 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go @@ -198,6 +198,7 @@ type JobStatus struct { Finished int64 `json:"finished,omitempty"` Message string `json:"message,omitempty"` Errors []string `json:"errors,omitempty"` + Warnings []string `json:"warnings,omitempty"` // Optional value 0-100 that can be set while running Progress float64 `json:"progress,omitempty"` @@ -225,18 +226,20 @@ type JobResourceSummary struct { Kind string `json:"kind,omitempty"` Total int64 `json:"total,omitempty"` // the count (if known) - Create int64 `json:"create,omitempty"` - Update int64 `json:"update,omitempty"` - Delete int64 `json:"delete,omitempty"` - Write int64 `json:"write,omitempty"` // Create or update (export) - Error int64 `json:"error,omitempty"` // The error count + Create int64 `json:"create,omitempty"` + Update int64 `json:"update,omitempty"` + Delete int64 `json:"delete,omitempty"` + Write int64 `json:"write,omitempty"` // Create or update (export) + Error int64 `json:"error,omitempty"` // The error count + Warning int64 `json:"warning,omitempty"` // The warning count // No action required (useful for sync) Noop int64 `json:"noop,omitempty"` - // Report errors for this resource type + // Report errors/warnings for this resource type // This may not be an exhaustive list and recommend looking at the logs for more info - Errors []string `json:"errors,omitempty"` + Errors []string `json:"errors,omitempty"` + Warnings []string `json:"warnings,omitempty"` } // HistoricJob is an append only log, saving all jobs that have been processed. diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go index 8a3def39e8d..4bf4f7674ff 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go @@ -401,6 +401,11 @@ func (in *JobResourceSummary) DeepCopyInto(out *JobResourceSummary) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Warnings != nil { + in, out := &in.Warnings, &out.Warnings + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -468,6 +473,11 @@ func (in *JobStatus) DeepCopyInto(out *JobStatus) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Warnings != nil { + in, out := &in.Warnings, &out.Warnings + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.Summary != nil { in, out := &in.Summary, &out.Summary *out = make([]*JobResourceSummary, len(*in)) diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go index 9a4a99d703a..933525eca0b 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go @@ -889,6 +889,13 @@ func schema_pkg_apis_provisioning_v0alpha1_JobResourceSummary(ref common.Referen Format: "int64", }, }, + "warning": { + SchemaProps: spec.SchemaProps{ + Description: "The error count", + Type: []string{"integer"}, + Format: "int64", + }, + }, "noop": { SchemaProps: spec.SchemaProps{ Description: "No action required (useful for sync)", @@ -898,7 +905,7 @@ func schema_pkg_apis_provisioning_v0alpha1_JobResourceSummary(ref common.Referen }, "errors": { SchemaProps: spec.SchemaProps{ - Description: "Report errors for this resource type This may not be an exhaustive list and recommend looking at the logs for more info", + Description: "Report errors/warnings for this resource type This may not be an exhaustive list and recommend looking at the logs for more info", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -911,6 +918,20 @@ func schema_pkg_apis_provisioning_v0alpha1_JobResourceSummary(ref common.Referen }, }, }, + "warnings": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, }, }, }, @@ -1029,6 +1050,20 @@ func schema_pkg_apis_provisioning_v0alpha1_JobStatus(ref common.ReferenceCallbac }, }, }, + "warnings": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, "progress": { SchemaProps: spec.SchemaProps{ Description: "Optional value 0-100 that can be set while running", diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list index c67b1462a9e..53071a05ec8 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -3,8 +3,10 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioni API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,FileList,Items API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,HistoryList,Items API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobResourceSummary,Errors +API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobResourceSummary,Warnings API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobStatus,Errors API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobStatus,Summary +API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobStatus,Warnings API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ManagerStats,Stats API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,MoveJobOptions,Paths API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,MoveJobOptions,Resources diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobresourcesummary.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobresourcesummary.go index ed6a62f651a..8986d2f85a6 100644 --- a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobresourcesummary.go +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobresourcesummary.go @@ -7,16 +7,18 @@ package v0alpha1 // JobResourceSummaryApplyConfiguration represents a declarative configuration of the JobResourceSummary type for use // with apply. type JobResourceSummaryApplyConfiguration struct { - Group *string `json:"group,omitempty"` - Kind *string `json:"kind,omitempty"` - Total *int64 `json:"total,omitempty"` - Create *int64 `json:"create,omitempty"` - Update *int64 `json:"update,omitempty"` - Delete *int64 `json:"delete,omitempty"` - Write *int64 `json:"write,omitempty"` - Error *int64 `json:"error,omitempty"` - Noop *int64 `json:"noop,omitempty"` - Errors []string `json:"errors,omitempty"` + Group *string `json:"group,omitempty"` + Kind *string `json:"kind,omitempty"` + Total *int64 `json:"total,omitempty"` + Create *int64 `json:"create,omitempty"` + Update *int64 `json:"update,omitempty"` + Delete *int64 `json:"delete,omitempty"` + Write *int64 `json:"write,omitempty"` + Error *int64 `json:"error,omitempty"` + Warning *int64 `json:"warning,omitempty"` + Noop *int64 `json:"noop,omitempty"` + Errors []string `json:"errors,omitempty"` + Warnings []string `json:"warnings,omitempty"` } // JobResourceSummaryApplyConfiguration constructs a declarative configuration of the JobResourceSummary type for use with @@ -89,6 +91,14 @@ func (b *JobResourceSummaryApplyConfiguration) WithError(value int64) *JobResour return b } +// WithWarning sets the Warning field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Warning field is set to the value of the last call. +func (b *JobResourceSummaryApplyConfiguration) WithWarning(value int64) *JobResourceSummaryApplyConfiguration { + b.Warning = &value + return b +} + // WithNoop sets the Noop field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Noop field is set to the value of the last call. @@ -106,3 +116,13 @@ func (b *JobResourceSummaryApplyConfiguration) WithErrors(values ...string) *Job } return b } + +// WithWarnings adds the given value to the Warnings field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Warnings field. +func (b *JobResourceSummaryApplyConfiguration) WithWarnings(values ...string) *JobResourceSummaryApplyConfiguration { + for i := range values { + b.Warnings = append(b.Warnings, values[i]) + } + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobstatus.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobstatus.go index ea9228473a5..0ad62090c62 100644 --- a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobstatus.go +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobstatus.go @@ -16,6 +16,7 @@ type JobStatusApplyConfiguration struct { Finished *int64 `json:"finished,omitempty"` Message *string `json:"message,omitempty"` Errors []string `json:"errors,omitempty"` + Warnings []string `json:"warnings,omitempty"` Progress *float64 `json:"progress,omitempty"` Summary []*provisioningv0alpha1.JobResourceSummary `json:"summary,omitempty"` URLs *RepositoryURLsApplyConfiguration `json:"url,omitempty"` @@ -69,6 +70,16 @@ func (b *JobStatusApplyConfiguration) WithErrors(values ...string) *JobStatusApp return b } +// WithWarnings adds the given value to the Warnings field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Warnings field. +func (b *JobStatusApplyConfiguration) WithWarnings(values ...string) *JobStatusApplyConfiguration { + for i := range values { + b.Warnings = append(b.Warnings, values[i]) + } + return b +} + // WithProgress sets the Progress field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Progress field is set to the value of the last call. diff --git a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts index 7ef1fc4fc91..3c6e905683c 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts @@ -1138,7 +1138,7 @@ export type JobResourceSummary = { delete?: number; /** Create or update (export) */ error?: number; - /** Report errors for this resource type This may not be an exhaustive list and recommend looking at the logs for more info */ + /** Report errors/warnings for this resource type This may not be an exhaustive list and recommend looking at the logs for more info */ errors?: string[]; group?: string; kind?: string; @@ -1146,6 +1146,9 @@ export type JobResourceSummary = { noop?: number; total?: number; update?: number; + /** The error count */ + warning?: number; + warnings?: string[]; write?: number; }; export type RepositoryUrLs = { @@ -1176,6 +1179,7 @@ export type JobStatus = { summary?: JobResourceSummary[]; /** URLs contains URLs for the reference branch or commit if applicable. */ url?: RepositoryUrLs; + warnings?: string[]; }; export type Job = { /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ diff --git a/pkg/registry/apis/provisioning/jobs/progress.go b/pkg/registry/apis/provisioning/jobs/progress.go index 97a293a5d8c..2cb9dc9ddcf 100644 --- a/pkg/registry/apis/provisioning/jobs/progress.go +++ b/pkg/registry/apis/provisioning/jobs/progress.go @@ -35,12 +35,13 @@ func maybeNotifyProgress(threshold time.Duration, fn ProgressFn) ProgressFn { // FIXME: ProgressRecorder should be initialized in the queue type JobResourceResult struct { - Name string - Group string - Kind string - Path string - Action repository.FileAction - Error error + Name string + Group string + Kind string + Path string + Action repository.FileAction + Error error + Warning error } type jobProgressRecorder struct { @@ -193,6 +194,10 @@ func (r *jobProgressRecorder) updateSummary(result JobResourceResult) { errorMsg := fmt.Sprintf("%s (file: %s, name: %s, action: %s)", result.Error.Error(), result.Path, result.Name, result.Action) summary.Errors = append(summary.Errors, errorMsg) summary.Error++ + } else if result.Warning != nil { + warningMsg := fmt.Sprintf("%s (file: %s, name: %s, action: %s)", result.Warning.Error(), result.Path, result.Name, result.Action) + summary.Warnings = append(summary.Warnings, warningMsg) + summary.Warning++ } else { switch result.Action { case repository.FileActionDeleted: @@ -266,8 +271,17 @@ func (r *jobProgressRecorder) Complete(ctx context.Context, err error) provision jobStatus.Message = err.Error() } - jobStatus.Summary = r.summary() + summaries := r.summary() + jobStatus.Summary = summaries jobStatus.Errors = r.errors + + // Extract warnings from summaries + warnings := make([]string, 0) + for _, summary := range summaries { + warnings = append(warnings, summary.Warnings...) + } + jobStatus.Warnings = warnings + jobStatus.URLs = r.refURLs tooManyErrors := r.maxErrors > 0 && r.errorCount >= r.maxErrors @@ -283,6 +297,9 @@ func (r *jobProgressRecorder) Complete(ctx context.Context, err error) provision jobStatus.Message = "completed with errors" jobStatus.State = provisioning.JobStateWarning } + } else if len(jobStatus.Warnings) > 0 { + jobStatus.State = provisioning.JobStateWarning + jobStatus.Message = "completed with warnings" } // Override message if progress have a more explicit message diff --git a/pkg/registry/apis/provisioning/jobs/progress_test.go b/pkg/registry/apis/provisioning/jobs/progress_test.go index caf44c767ff..611058d2c74 100644 --- a/pkg/registry/apis/provisioning/jobs/progress_test.go +++ b/pkg/registry/apis/provisioning/jobs/progress_test.go @@ -2,9 +2,11 @@ package jobs import ( "context" + "errors" "testing" provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + "github.com/grafana/grafana/apps/provisioning/pkg/repository" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -83,3 +85,167 @@ func TestJobProgressRecorderCompleteIncludesRefURLs(t *testing.T) { assert.Equal(t, provisioning.JobStateSuccess, finalStatus.State) assert.Equal(t, "completed successfully", finalStatus.Message) } + +func TestJobProgressRecorderWarningStatus(t *testing.T) { + ctx := context.Background() + + // Create a progress recorder + mockProgressFn := func(ctx context.Context, status provisioning.JobStatus) error { + return nil + } + recorder := newJobProgressRecorder(mockProgressFn).(*jobProgressRecorder) + + // Record a result with a warning + warningErr := errors.New("deprecated API used") + result := JobResourceResult{ + Name: "test-resource", + Group: "test.grafana.app", + Kind: "Dashboard", + Path: "dashboards/test.json", + Action: repository.FileActionUpdated, + Warning: warningErr, + } + recorder.Record(ctx, result) + + // Record another result with a different warning + warningErr2 := errors.New("missing optional field") + result2 := JobResourceResult{ + Name: "test-resource-2", + Group: "test.grafana.app", + Kind: "Dashboard", + Path: "dashboards/test2.json", + Action: repository.FileActionCreated, + Warning: warningErr2, + } + recorder.Record(ctx, result2) + + // Record a result with a warning from a different resource type + warningErr3 := errors.New("validation warning") + result3 := JobResourceResult{ + Name: "test-resource-3", + Group: "test.grafana.app", + Kind: "DataSource", + Path: "datasources/test.yaml", + Action: repository.FileActionCreated, + Warning: warningErr3, + } + recorder.Record(ctx, result3) + + // Verify warnings are stored in summaries + recorder.mu.RLock() + require.Len(t, recorder.summaries, 2) // Dashboard and DataSource + dashboardSummary := recorder.summaries["test.grafana.app:Dashboard"] + require.NotNil(t, dashboardSummary) + assert.Equal(t, int64(2), dashboardSummary.Warning) + assert.Len(t, dashboardSummary.Warnings, 2) + assert.Contains(t, dashboardSummary.Warnings[0], "deprecated API used") + assert.Contains(t, dashboardSummary.Warnings[1], "missing optional field") + + datasourceSummary := recorder.summaries["test.grafana.app:DataSource"] + require.NotNil(t, datasourceSummary) + assert.Equal(t, int64(1), datasourceSummary.Warning) + assert.Len(t, datasourceSummary.Warnings, 1) + assert.Contains(t, datasourceSummary.Warnings[0], "validation warning") + recorder.mu.RUnlock() + + // Complete the job + finalStatus := recorder.Complete(ctx, nil) + + // Verify the final status includes warnings + require.NotNil(t, finalStatus.Warnings) + assert.Len(t, finalStatus.Warnings, 3) + assert.Contains(t, finalStatus.Warnings[0], "deprecated API used") + assert.Contains(t, finalStatus.Warnings[1], "missing optional field") + assert.Contains(t, finalStatus.Warnings[2], "validation warning") + + // Verify the state is set to Warning + assert.Equal(t, provisioning.JobStateWarning, finalStatus.State) + assert.Equal(t, "completed with warnings", finalStatus.Message) + + // Verify summaries are included + require.Len(t, finalStatus.Summary, 2) + + // Verify no errors were recorded + assert.Empty(t, finalStatus.Errors) +} + +func TestJobProgressRecorderWarningWithErrors(t *testing.T) { + ctx := context.Background() + + // Create a progress recorder + mockProgressFn := func(ctx context.Context, status provisioning.JobStatus) error { + return nil + } + recorder := newJobProgressRecorder(mockProgressFn).(*jobProgressRecorder) + + // Record a result with an error (errors take precedence) + errorErr := errors.New("failed to process") + result := JobResourceResult{ + Name: "test-resource", + Group: "test.grafana.app", + Kind: "Dashboard", + Path: "dashboards/test.json", + Action: repository.FileActionUpdated, + Error: errorErr, + } + recorder.Record(ctx, result) + + // Record a result with only a warning + warningErr := errors.New("deprecated API used") + result2 := JobResourceResult{ + Name: "test-resource-2", + Group: "test.grafana.app", + Kind: "Dashboard", + Path: "dashboards/test2.json", + Action: repository.FileActionCreated, + Warning: warningErr, + } + recorder.Record(ctx, result2) + + // Complete the job + finalStatus := recorder.Complete(ctx, nil) + + // When there are errors, the state should be Warning (not Error unless too many) + // and warnings should still be included + assert.Equal(t, provisioning.JobStateWarning, finalStatus.State) + assert.Equal(t, "completed with errors", finalStatus.Message) + assert.Len(t, finalStatus.Errors, 1) + assert.Contains(t, finalStatus.Errors[0], "failed to process") + + // Warnings should still be extracted from summaries + require.NotNil(t, finalStatus.Warnings) + assert.Len(t, finalStatus.Warnings, 1) + assert.Contains(t, finalStatus.Warnings[0], "deprecated API used") +} + +func TestJobProgressRecorderWarningOnlyNoErrors(t *testing.T) { + ctx := context.Background() + + // Create a progress recorder + mockProgressFn := func(ctx context.Context, status provisioning.JobStatus) error { + return nil + } + recorder := newJobProgressRecorder(mockProgressFn).(*jobProgressRecorder) + + // Record only warnings, no errors + warningErr := errors.New("deprecated API used") + result := JobResourceResult{ + Name: "test-resource", + Group: "test.grafana.app", + Kind: "Dashboard", + Path: "dashboards/test.json", + Action: repository.FileActionUpdated, + Warning: warningErr, + } + recorder.Record(ctx, result) + + // Complete the job + finalStatus := recorder.Complete(ctx, nil) + + // Verify the state is Warning (not Error) when only warnings exist + assert.Equal(t, provisioning.JobStateWarning, finalStatus.State) + assert.Equal(t, "completed with warnings", finalStatus.Message) + assert.Empty(t, finalStatus.Errors) + require.NotNil(t, finalStatus.Warnings) + assert.Len(t, finalStatus.Warnings, 1) +} diff --git a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json index f99b8f60738..ab232f8674c 100644 --- a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json @@ -3696,7 +3696,7 @@ "format": "int64" }, "errors": { - "description": "Report errors for this resource type This may not be an exhaustive list and recommend looking at the logs for more info", + "description": "Report errors/warnings for this resource type This may not be an exhaustive list and recommend looking at the logs for more info", "type": "array", "items": { "type": "string", @@ -3722,6 +3722,18 @@ "type": "integer", "format": "int64" }, + "warning": { + "description": "The error count", + "type": "integer", + "format": "int64" + }, + "warnings": { + "type": "array", + "items": { + "type": "string", + "default": "" + } + }, "write": { "type": "integer", "format": "int64" @@ -3849,6 +3861,13 @@ "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.RepositoryURLs" } ] + }, + "warnings": { + "type": "array", + "items": { + "type": "string", + "default": "" + } } } }, From 1addfd69b45afc3ca1a0f7a37bf9df33327f9fd4 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Fri, 12 Dec 2025 17:00:40 +0200 Subject: [PATCH 068/297] Provisioning: Fix duplicated breadcrumb (#115234) * Provisioning: Fix duplicated breadcrumb * translations --- .../GettingStarted/GettingStartedPage.tsx | 11 ++++------- public/locales/en-US/grafana.json | 1 - 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/public/app/features/provisioning/GettingStarted/GettingStartedPage.tsx b/public/app/features/provisioning/GettingStarted/GettingStartedPage.tsx index adaf68a80ee..0235ae86e92 100644 --- a/public/app/features/provisioning/GettingStarted/GettingStartedPage.tsx +++ b/public/app/features/provisioning/GettingStarted/GettingStartedPage.tsx @@ -15,13 +15,10 @@ export default function GettingStartedPage({ items }: Props) { return ( diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 6c760e94814..9d9ec201d18 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -11867,7 +11867,6 @@ "title-setting-connection-could-cause-temporary-outage": "Setting up this connection could cause a temporary outage" }, "getting-started-page": { - "header": "Provisioning", "subtitle-provisioning-feature": "View and manage your provisioning connections" }, "git": { From f3421b97189769c41b8ccd39b75fbce5aa8331bb Mon Sep 17 00:00:00 2001 From: Anna Urbiztondo Date: Fri, 12 Dec 2025 16:08:28 +0100 Subject: [PATCH 069/297] Docs: Git Sync scenarios (#115199) * WIP * Review * Move scenarions * Structure fix * Edits, fix * Vale, x-refs * Feedback, tweaks * Consolidate HA, titles * Prettier * Prettier * Adding missing content * Minor edits * Links * Prettier --- .../manage-dashboards-argocd.md | 2 +- .../git-sync-deployment-scenarios/_index.md | 147 ++++++++++++ .../git-sync-deployment-scenarios/dev-prod.md | 147 ++++++++++++ .../high-availability.md | 217 ++++++++++++++++++ .../multi-region.md | 93 ++++++++ .../multi-team.md | 169 ++++++++++++++ .../single-instance.md | 86 +++++++ .../provision-resources/git-sync-setup.md | 1 + .../provision-resources/intro-git-sync.md | 8 +- .../provision-resources/use-git-sync.md | 2 +- 10 files changed, 869 insertions(+), 3 deletions(-) create mode 100644 docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/_index.md create mode 100644 docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/dev-prod.md create mode 100644 docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/high-availability.md create mode 100644 docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/multi-region.md create mode 100644 docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/multi-team.md create mode 100644 docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/single-instance.md diff --git a/docs/sources/as-code/infrastructure-as-code/grafana-operator/manage-dashboards-argocd.md b/docs/sources/as-code/infrastructure-as-code/grafana-operator/manage-dashboards-argocd.md index 0531a0c803f..bfc369222c8 100644 --- a/docs/sources/as-code/infrastructure-as-code/grafana-operator/manage-dashboards-argocd.md +++ b/docs/sources/as-code/infrastructure-as-code/grafana-operator/manage-dashboards-argocd.md @@ -81,7 +81,7 @@ Replace the placeholders with your values: In your `grafana` directory, create a sub-folder called `dashboards`. -This guide shows you how to creates three separate dashboards. For all dashboard configurations, replace the placeholders with your values: +This guide shows you how to create three separate dashboards. For all dashboard configurations, replace the placeholders with your values: - _``_: Name of your Grafana Cloud Stack - _``_: Namespace where the `grafana-operator` is deployed in your Kubernetes cluster diff --git a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/_index.md b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/_index.md new file mode 100644 index 00000000000..4e4e523bf35 --- /dev/null +++ b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/_index.md @@ -0,0 +1,147 @@ +--- +title: Git Sync deployment scenarios +menuTitle: Deployment scenarios +description: Learn about common Git Sync deployment patterns and configurations for different organizational needs +weight: 450 +keywords: + - git sync + - deployment patterns + - scenarios + - multi-environment + - teams +--- + +# Git Sync deployment scenarios + +This guide shows practical deployment scenarios for Grafana’s Git Sync. Learn how to configure bidirectional synchronization between Grafana and Git repositories for teams, environments, and regions. + +{{< admonition type="caution" >}} +Git Sync is an experimental feature. It reflects Grafana’s approach to Observability as Code and might include limitations or breaking changes. For current status and known limitations, refer to the [Git Sync introduction](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/intro-git-sync/). +{{< /admonition >}} + +## Understand the relationship between key Git Sync components + +Before you explore the scenarios, understand how the key Git Sync components relate: + +- [Grafana instance](#grafana-instance) +- [Git repository structure](#git-repository-structure) +- [Git Sync repository resource](#git-sync-repository-resource) + +### Grafana instance + +A Grafana instance is a running Grafana server. Multiple instances can: + +- Connect to the same Git repository using different Repository configurations. +- Sync from different branches of the same repository. +- Sync from different paths within the same repository. +- Sync from different repositories. + +### Git repository structure + +You can organize your Git repository in several ways: + +- Single branch, multiple paths: Use different directories for different purposes (for example, `dev/`, `prod/`, `team-a/`). +- Multiple branches: Use different branches for different environments or teams (for example, `main`, `develop`, `team-a`). +- Multiple repositories: Use separate repositories for different teams or environments. + +### Git Sync repository resource + +A repository resource is a Grafana configuration object that defines: + +- Which Git repository to sync with. +- Which branch to use. +- Which directory path to synchronize. +- Sync behavior and workflows. + +Each repository resource creates bidirectional synchronization between a Grafana instance and a specific location in Git. + +## How does repository sync behave? + +With Git Sync you configure a repository resource to sync with your Grafana instance: + +1. Grafana monitors the specified Git location (repository, branch, and path). +2. Grafana creates a folder in Dashboards (typically named after the repository). +3. Grafana creates dashboards from dashboard JSON files in Git within this folder. +4. Grafana commits dashboard changes made in the UI back to Git. +5. Grafana pulls dashboard changes made in Git and updates dashboards in the UI. +6. Synchronization occurs at regular intervals (configurable), or instantly if you use webhooks. + +You can find the provisioned dashboards organized in folders under **Dashboards**. + +## Example: Relationship between repository, branch, and path + +Here's a concrete example showing how the three parameters work together: + +**Configuration:** + +- **Repository**: `your-org/grafana-manifests` +- **Branch**: `main` +- **Path**: `team-platform/grafana/` + +**In Git (on branch `main`):** + +``` +your-org/grafana-manifests/ +├── .git/ +├── README.md +├── team-platform/ +│ └── grafana/ +│ ├── cpu-metrics.json ← Synced +│ ├── memory-usage.json ← Synced +│ └── disk-io.json ← Synced +├── team-data/ +│ └── grafana/ +│ └── pipeline-stats.json ← Not synced (different path) +└── other-files.txt ← Not synced (outside path) +``` + +**In Grafana Dashboards view:** + +``` +Dashboards +└── 📁 grafana-manifests/ + ├── CPU Metrics Dashboard + ├── Memory Usage Dashboard + └── Disk I/O Dashboard +``` + +**Key points:** + +- Grafana only synchronizes files within the specified path (`team-platform/grafana/`). +- Grafana ignores files in other paths or at the repository root. +- The folder name in Grafana comes from the repository name. +- Dashboard titles come from the JSON file content, not the filename. + +## Repository configuration flexibility + +Git Sync repositories support different combinations of repository URL, branch, and path: + +- Different Git repositories: Each environment or team can use its own repository. + - Instance A: `repository: your-org/grafana-prod`. + - Instance B: `repository: your-org/grafana-dev`. +- Different branches: Use separate branches within the same repository. + - Instance A: `repository: your-org/grafana-manifests, branch: main`. + - Instance B: `repository: your-org/grafana-manifests, branch: develop`. +- Different paths: Use different directory paths within the same repository. + - Instance A: `repository: your-org/grafana-manifests, branch: main, path: production/`. + - Instance B: `repository: your-org/grafana-manifests, branch: main, path: development/`. +- Any combination: Mix and match based on your workflow requirements. + +## Scenarios + +Use these deployment scenarios to plan your Git Sync setup: + +- [Single instance](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/single-instance/) +- [Git Sync for development and production environments](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/dev-prod/) +- [Git Sync with regional replication](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/multi-region/) +- [High availability](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/high-availability/) +- [Git Sync in a shared Grafana instance](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/multi-team/) + +## Learn more + +Refer to the following documents to learn more: + +- [Git Sync introduction](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/intro-git-sync/) +- [Git Sync setup guide](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/git-sync-setup/) +- [Dashboard provisioning](https://grafana.com/docs/grafana//administration/provisioning/) +- [Observability as Code](https://grafana.com/docs/grafana//as-code/observability-as-code/) diff --git a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/dev-prod.md b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/dev-prod.md new file mode 100644 index 00000000000..3433c713024 --- /dev/null +++ b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/dev-prod.md @@ -0,0 +1,147 @@ +--- +title: Git Sync for development and production environments +menuTitle: Across environments +description: Use separate Grafana instances for development and production with Git-controlled promotion +weight: 20 +--- + +# Git Sync for development and production environments + +Use separate Grafana instances for development and production. Each syncs with different Git locations to test dashboards before production. + +## Use it for + +- **Staged deployments**: You need to test dashboard changes before production deployment. +- **Change control**: You require approvals before dashboards reach production. +- **Quality assurance**: You verify dashboard functionality in a non-production environment. +- **Risk mitigation**: You minimize the risk of breaking production dashboards. + +## Architecture + +``` +┌────────────────────────────────────────────────────────────┐ +│ GitHub Repository │ +│ Repository: your-org/grafana-manifests │ +│ Branch: main │ +│ │ +│ grafana-manifests/ │ +│ ├── dev/ │ +│ │ ├── dashboard-new.json ← Development dashboards │ +│ │ └── dashboard-test.json │ +│ │ │ +│ └── prod/ │ +│ ├── dashboard-stable.json ← Production dashboards │ +│ └── dashboard-approved.json │ +└────────────────────────────────────────────────────────────┘ + ↕ ↕ + Git Sync (dev/) Git Sync (prod/) + ↕ ↕ +┌─────────────────────┐ ┌─────────────────────┐ +│ Dev Grafana │ │ Prod Grafana │ +│ │ │ │ +│ Repository: │ │ Repository: │ +│ - path: dev/ │ │ - path: prod/ │ +│ │ │ │ +│ Creates folder: │ │ Creates folder: │ +│ "grafana-manifests"│ │ "grafana-manifests"│ +└─────────────────────┘ └─────────────────────┘ +``` + +## Repository structure + +**In Git:** + +``` +your-org/grafana-manifests +├── dev/ +│ ├── dashboard-new.json +│ └── dashboard-test.json +└── prod/ + ├── dashboard-stable.json + └── dashboard-approved.json +``` + +**In Grafana Dashboards view:** + +**Dev instance:** + +``` +Dashboards +└── 📁 grafana-manifests/ + ├── New Dashboard + └── Test Dashboard +``` + +**Prod instance:** + +``` +Dashboards +└── 📁 grafana-manifests/ + ├── Stable Dashboard + └── Approved Dashboard +``` + +- Both instances create a folder named "grafana-manifests" (from repository name) +- Each instance only shows dashboards from its configured path (`dev/` or `prod/`) +- Dashboards appear with their titles from the JSON files + +## Configuration parameters + +Development: + +- Repository: `your-org/grafana-manifests` +- Branch: `main` +- Path: `dev/` + +Production: + +- Repository: `your-org/grafana-manifests` +- Branch: `main` +- Path: `prod/` + +## How it works + +1. Developers create and modify dashboards in development. +2. Git Sync commits changes to `dev/`. +3. You review changes in Git. +4. You promote approved dashboards from `dev/` to `prod/`. +5. Production syncs from `prod/`. +6. Production dashboards update. + +## Alternative: Use branches + +Instead of using different paths, you can configure instances to use different branches: + +**Development instance:** + +- **Repository**: `your-org/grafana-manifests` +- **Branch**: `develop` +- **Path**: `grafana/` + +**Production instance:** + +- **Repository**: `your-org/grafana-manifests` +- **Branch**: `main` +- **Path**: `grafana/` + +With this approach: + +- Development changes go to the `develop` branch +- Use Git merge or pull request workflows to promote changes from `develop` to `main` +- Production automatically syncs from the `main` branch + +## Alternative: Use separate repositories for stricter isolation + +For stricter isolation, use completely separate repositories: + +**Development instance:** + +- **Repository**: `your-org/grafana-manifests-dev` +- **Branch**: `main` +- **Path**: `grafana/` + +**Production instance:** + +- **Repository**: `your-org/grafana-manifests-prod` +- **Branch**: `main` +- **Path**: `grafana/` diff --git a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/high-availability.md b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/high-availability.md new file mode 100644 index 00000000000..04575574ed4 --- /dev/null +++ b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/high-availability.md @@ -0,0 +1,217 @@ +--- +title: Git Sync for high availability environments +menuTitle: High availability +description: Run multiple Grafana instances serving traffic simultaneously, synchronized via Git Sync +weight: 50 +--- + +# Git Sync for high availability environments + +## Primary–replica scenario + +Use a primary Grafana instance and one or more replicas synchronized with the same Git location to enable failover. + +### Use it for + +- **Automatic failover**: You need service continuity when the primary instance fails. +- **High availability**: Your organization requires guaranteed dashboard availability. +- **Simple HA setup**: You want high availability without the complexity of active–active. +- **Maintenance windows**: You perform updates while another instance serves traffic. +- **Business continuity**: Dashboard access can't tolerate downtime. + +### Architecture + +``` +┌─────────────────────────────────────────────────────┐ +│ GitHub Repository │ +│ Repository: your-org/grafana-manifests │ +│ Branch: main │ +│ │ +│ grafana-manifests/ │ +│ └── shared/ │ +│ ├── dashboard-metrics.json │ +│ ├── dashboard-alerts.json │ +│ └── dashboard-logs.json │ +└─────────────────────────────────────────────────────┘ + ↕ ↕ + Git Sync (shared/) Git Sync (shared/) + ↕ ↕ +┌────────────────────┐ ┌────────────────────┐ +│ Master Grafana │ │ Replica Grafana │ +│ (Active) │ │ (Standby) │ +│ │ │ │ +│ Repository: │ │ Repository: │ +│ - path: shared/ │ │ - path: shared/ │ +└────────────────────┘ └────────────────────┘ + │ │ + └───────────┬───────────────────┘ + ↓ + ┌──────────────────────┐ + │ Reverse Proxy │ + │ (Failover) │ + └──────────────────────┘ +``` + +### Repository structure + +**In Git:** + +``` +your-org/grafana-manifests +└── shared/ + ├── dashboard-metrics.json + ├── dashboard-alerts.json + └── dashboard-logs.json +``` + +**In Grafana Dashboards view (both instances):** + +``` +Dashboards +└── 📁 grafana-manifests/ + ├── Metrics Dashboard + ├── Alerts Dashboard + └── Logs Dashboard +``` + +- Master and replica instances show identical folder structure. +- Both sync from the same `shared/` path. +- Reverse proxy routes traffic to master (active) instance. +- If master fails, proxy automatically fails over to replica (standby). +- Users see the same dashboards regardless of which instance is serving traffic. + +### Configuration parameters + +Both master and replica instances use identical parameters: + +**Master instance:** + +- **Repository**: `your-org/grafana-manifests` +- **Branch**: `main` +- **Path**: `shared/` + +**Replica instance:** + +- **Repository**: `your-org/grafana-manifests` +- **Branch**: `main` +- **Path**: `shared/` + +### How it works + +1. Both instances stay synchronized through Git. +2. Reverse proxy routes traffic to primary. +3. Users edit on primary. Git Sync commits changes. +4. Both instances pull latest changes to keep replica in sync. +5. On primary failure, proxy fails over to replica. + +### Failover considerations + +- Health checks and monitoring. +- Continuous syncing to minimize data loss. +- Plan failback (automatic or manual). + +## Load balancer scenario + +Run multiple active Grafana instances behind a load balancer. All instances sync from the same Git location. + +### Use it for + +- **High traffic**: Your deployment needs to handle significant user load. +- **Load distribution**: You want to distribute user requests across instances. +- **Maximum availability**: You need service continuity during maintenance or failures. +- **Scalability**: You want to add instances as load increases. +- **Performance**: Users need fast response times under heavy load. + +### Architecture + +``` +┌─────────────────────────────────────────────────────┐ +│ GitHub Repository │ +│ Repository: your-org/grafana-manifests │ +│ Branch: main │ +│ │ +│ grafana-manifests/ │ +│ └── shared/ │ +│ ├── dashboard-metrics.json │ +│ ├── dashboard-alerts.json │ +│ └── dashboard-logs.json │ +└─────────────────────────────────────────────────────┘ + ↕ ↕ + Git Sync (shared/) Git Sync (shared/) + ↕ ↕ +┌────────────────────┐ ┌────────────────────┐ +│ Grafana Instance 1│ │ Grafana Instance 2│ +│ (Active) │ │ (Active) │ +│ │ │ │ +│ Repository: │ │ Repository: │ +│ - path: shared/ │ │ - path: shared/ │ +└────────────────────┘ └────────────────────┘ + │ │ + └───────────┬───────────────────┘ + ↓ + ┌──────────────────────┐ + │ Load Balancer │ + │ (Round Robin) │ + └──────────────────────┘ +``` + +### Repository structure + +**In Git:** + +``` +your-org/grafana-manifests +└── shared/ + ├── dashboard-metrics.json + ├── dashboard-alerts.json + └── dashboard-logs.json +``` + +**In Grafana Dashboards view (all instances):** + +``` +Dashboards +└── 📁 grafana-manifests/ + ├── Metrics Dashboard + ├── Alerts Dashboard + └── Logs Dashboard +``` + +- All instances show identical folder structure. +- All instances sync from the same `shared/` path. +- Load balancer distributes requests across all active instances. +- Any instance can serve read requests. +- Any instance can accept dashboard modifications. +- Changes propagate to all instances through Git. + +### Configuration parameters + +All instances use identical parameters: + +**Instance 1:** + +- **Repository**: `your-org/grafana-manifests` +- **Branch**: `main` +- **Path**: `shared/` + +**Instance 2:** + +- **Repository**: `your-org/grafana-manifests` +- **Branch**: `main` +- **Path**: `shared/` + +### How it works + +1. All instances stay synchronized through Git. +2. Load balancer distributes incoming traffic across all active instances. +3. Users can view dashboards from any instance. +4. When a user modifies a dashboard on any instance, Git Sync commits the change. +5. All other instances pull the updated dashboard during their next sync cycle, or instantly if webhooks are configured. +6. If one instance fails, load balancer stops routing traffic to it and remaining instances continue serving. + +### Important considerations + +- **Eventually consistent**: Due to sync intervals, instances may briefly have different dashboard versions. +- **Concurrent edits**: Multiple users editing the same dashboard on different instances can cause conflicts. +- **Database sharing**: Instances should share the same backend database for user sessions, preferences, and annotations. +- **Stateless design**: Design for stateless operation where possible to maximize load balancing effectiveness. diff --git a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/multi-region.md b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/multi-region.md new file mode 100644 index 00000000000..ee699719560 --- /dev/null +++ b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/multi-region.md @@ -0,0 +1,93 @@ +--- +title: Git Sync with regional replication +menuTitle: Regional replication +description: Synchronize multiple regional Grafana instances from a shared Git location +weight: 30 +--- + +# Git Sync with regional replication + +Deploy multiple Grafana instances across regions. Synchronize them with the same Git location to ensure consistent dashboards everywhere. + +## Use it for + +- **Geographic distribution**: You deploy Grafana close to users in different regions. +- **Latency reduction**: Users need fast dashboard access from their location. +- **Data sovereignty**: You keep dashboard data in specific regions. +- **High availability**: You need dashboard availability across regions. +- **Consistent experience**: All users see the same dashboards regardless of region. + +## Architecture + +``` +┌─────────────────────────────────────────────────────┐ +│ GitHub Repository │ +│ Repository: your-org/grafana-manifests │ +│ Branch: main │ +│ │ +│ grafana-manifests/ │ +│ └── shared/ │ +│ ├── dashboard-global.json │ +│ ├── dashboard-metrics.json │ +│ └── dashboard-logs.json │ +└─────────────────────────────────────────────────────┘ + ↕ ↕ + Git Sync (shared/) Git Sync (shared/) + ↕ ↕ +┌────────────────────┐ ┌────────────────────┐ +│ US Region │ │ EU Region │ +│ Grafana │ │ Grafana │ +│ │ │ │ +│ Repository: │ │ Repository: │ +│ - path: shared/ │ │ - path: shared/ │ +└────────────────────┘ └────────────────────┘ +``` + +## Repository structure + +**In Git:** + +``` +your-org/grafana-manifests +└── shared/ + ├── dashboard-global.json + ├── dashboard-metrics.json + └── dashboard-logs.json +``` + +**In Grafana Dashboards view (all regions):** + +``` +Dashboards +└── 📁 grafana-manifests/ + ├── Global Dashboard + ├── Metrics Dashboard + └── Logs Dashboard +``` + +- All regional instances (US, EU, etc.) show identical folder structure +- Same folder name "grafana-manifests" in every region +- Same dashboards synced from the `shared/` path appear everywhere +- Users in any region see the exact same dashboards with the same titles + +## Configuration parameters + +All regions: + +- Repository: `your-org/grafana-manifests` +- Branch: `main` +- Path: `shared/` + +## How it works + +1. All regional instances pull dashboards from `shared/`. +2. Any region’s change commits to Git. +3. Other regions pull updates during the next sync (or via webhooks). +4. Changes propagate across regions per sync interval. + +## Considerations + +- **Write conflicts**: If users in different regions modify the same dashboard simultaneously, Git uses last-write-wins. +- **Primary region**: Consider designating one region as the primary location for making dashboard changes. +- **Propagation time**: Changes propagate to all regions within the configured sync interval, or instantly if webhooks are configured. +- **Network reliability**: Ensure all regions have reliable connectivity to the Git repository. diff --git a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/multi-team.md b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/multi-team.md new file mode 100644 index 00000000000..4f5ecc85fd2 --- /dev/null +++ b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/multi-team.md @@ -0,0 +1,169 @@ +--- +title: Multiple team Git Sync +menuTitle: Shared instance +description: Use multiple Git repositories with one Grafana instance, one repository per team +weight: 60 +--- + +# Git Sync in a Grafana instance shared by multiple teams + +Use a single Grafana instance with multiple Repository resources, one per team. Each team manages its own dashboards while sharing Grafana. + +## Use it for + +- **Team autonomy**: Different teams manage their own dashboards independently. +- **Organizational structure**: Dashboard organization aligns with team structure. +- **Resource efficiency**: Multiple teams share Grafana infrastructure. +- **Cost optimization**: You reduce infrastructure costs while maintaining team separation. +- **Collaboration**: Teams can view each other’s dashboards while managing their own. + +## Architecture + +``` +┌─────────────────────────┐ ┌─────────────────────────┐ +│ Platform Team Repo │ │ Data Team Repo │ +│ platform-dashboards │ │ data-dashboards │ +│ │ │ │ +│ platform-dashboards/ │ │ data-dashboards/ │ +│ └── grafana/ │ │ └── grafana/ │ +│ ├── k8s.json │ │ ├── pipeline.json │ +│ └── infra.json │ │ └── analytics.json │ +└─────────────────────────┘ └─────────────────────────┘ + ↕ ↕ + Git Sync (grafana/) Git Sync (grafana/) + ↕ ↕ + ┌──────────────────────────────────────┐ + │ Grafana Instance │ + │ │ + │ Repository 1: │ + │ - repo: platform-dashboards │ + │ → Creates "platform-dashboards" │ + │ │ + │ Repository 2: │ + │ - repo: data-dashboards │ + │ → Creates "data-dashboards" │ + └──────────────────────────────────────┘ +``` + +## Repository structure + +**In Git (separate repositories):** + +**Platform team repository:** + +``` +your-org/platform-dashboards +└── grafana/ + ├── dashboard-k8s.json + └── dashboard-infra.json +``` + +**Data team repository:** + +``` +your-org/data-dashboards +└── grafana/ + ├── dashboard-pipeline.json + └── dashboard-analytics.json +``` + +**In Grafana Dashboards view:** + +``` +Dashboards +├── 📁 platform-dashboards/ +│ ├── Kubernetes Dashboard +│ └── Infrastructure Dashboard +└── 📁 data-dashboards/ + ├── Pipeline Dashboard + └── Analytics Dashboard +``` + +- Two separate folders created (one per Repository resource). +- Folder names derived from repository names. +- Each team has complete control over their own repository. +- Teams can independently manage permissions, branches, and workflows in their repos. +- All teams can view each other's dashboards in Grafana but manage only their own. + +## Configuration parameters + +**Platform team repository:** + +- **Repository**: `your-org/platform-dashboards` +- **Branch**: `main` +- **Path**: `grafana/` + +**Data team repository:** + +- **Repository**: `your-org/data-dashboards` +- **Branch**: `main` +- **Path**: `grafana/` + +## How it works + +1. Each team has their own Git repository for complete autonomy. +2. Each repository resource in Grafana creates a separate folder. +3. Platform team dashboards sync from `your-org/platform-dashboards` repository. +4. Data team dashboards sync from `your-org/data-dashboards` repository. +5. Teams can independently manage their repository settings, access controls, and workflows. +6. All teams can view each other's dashboards in Grafana but edit only their own. + +## Scale to more teams + +Adding additional teams is straightforward. For a third team, create a new repository and configure: + +- **Repository**: `your-org/security-dashboards` +- **Branch**: `main` +- **Path**: `grafana/` + +This creates a new "security-dashboards" folder in the same Grafana instance. + +## Alternative: Shared repository with different paths + +For teams that prefer sharing a single repository, use different paths to separate team dashboards: + +**In Git:** + +``` +your-org/grafana-manifests +├── team-platform/ +│ ├── dashboard-k8s.json +│ └── dashboard-infra.json +└── team-data/ + ├── dashboard-pipeline.json + └── dashboard-analytics.json +``` + +**Configuration:** + +**Platform team:** + +- **Repository**: `your-org/grafana-manifests` +- **Branch**: `main` +- **Path**: `team-platform/` + +**Data team:** + +- **Repository**: `your-org/grafana-manifests` +- **Branch**: `main` +- **Path**: `team-data/` + +This approach provides simpler repository management but less isolation between teams. + +## Alternative: Different branches per team + +For teams wanting their own branch in a shared repository: + +**Platform team:** + +- **Repository**: `your-org/grafana-manifests` +- **Branch**: `team-platform` +- **Path**: `grafana/` + +**Data team:** + +- **Repository**: `your-org/grafana-manifests` +- **Branch**: `team-data` +- **Path**: `grafana/` + +This allows teams to use Git branch workflows for collaboration while sharing the same repository. diff --git a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/single-instance.md b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/single-instance.md new file mode 100644 index 00000000000..39371f7e7d6 --- /dev/null +++ b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios/single-instance.md @@ -0,0 +1,86 @@ +--- +title: Single instance Git Sync +menuTitle: Single instance +description: Synchronize a single Grafana instance with a Git repository +weight: 10 +--- + +# Single instance Git Sync + +Use a single Grafana instance synchronized with a Git repository. This is the foundation for Git Sync and helps you understand bidirectional synchronization. + +## Use it for + +- **Getting started**: You want to learn how Git Sync works before implementing complex scenarios. +- **Personal projects**: Individual developers manage their own dashboards. +- **Small teams**: You have a simple setup without multiple environments or complex workflows. +- **Development environments**: You need quick prototyping and testing. + +## Architecture + +``` +┌─────────────────────────────────────────────────────┐ +│ GitHub Repository │ +│ Repository: your-org/grafana-manifests │ +│ Branch: main │ +│ │ +│ grafana-manifests/ │ +│ └── grafana/ │ +│ ├── dashboard-1.json │ +│ ├── dashboard-2.json │ +│ └── dashboard-3.json │ +└─────────────────────────────────────────────────────┘ + ↕ + Git Sync (bidirectional) + ↕ + ┌─────────────────────────────┐ + │ Grafana Instance │ + │ │ + │ Repository Resource: │ + │ - url: grafana-manifests │ + │ - branch: main │ + │ - path: grafana/ │ + │ │ + │ Creates folder: │ + │ "grafana-manifests" │ + └─────────────────────────────┘ +``` + +## Repository structure + +**In Git:** + +``` +your-org/grafana-manifests +└── grafana/ + ├── dashboard-1.json + ├── dashboard-2.json + └── dashboard-3.json +``` + +**In Grafana Dashboards view:** + +``` +Dashboards +└── 📁 grafana-manifests/ + ├── Dashboard 1 + ├── Dashboard 2 + └── Dashboard 3 +``` + +- A folder named "grafana-manifests" (from repository name) contains all synced dashboards. +- Each JSON file becomes a dashboard with its title displayed in the folder. +- Users browse dashboards organized under this folder structure. + +## Configuration parameters + +Configure your Grafana instance to synchronize with: + +- **Repository**: `your-org/grafana-manifests` +- **Branch**: `main` +- **Path**: `grafana/` + +## How it works + +1. **From Grafana to Git**: When users create or modify dashboards in Grafana, Git Sync commits changes to the `grafana/` directory on the `main` branch. +2. **From Git to Grafana**: When dashboard JSON files are added or modified in the `grafana/` directory, Git Sync pulls these changes into Grafana. diff --git a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md index 553027f4132..cd07c532dae 100644 --- a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md +++ b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md @@ -367,5 +367,6 @@ To learn more about using Git Sync: - [Work with provisioned dashboards](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/provisioned-dashboards/) - [Manage provisioned repositories with Git Sync](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/use-git-sync/) +- [Git Sync deployment scenarios](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios) - [Export resources](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/export-resources/) - [grafanactl documentation](https://grafana.github.io/grafanactl/) diff --git a/docs/sources/as-code/observability-as-code/provision-resources/intro-git-sync.md b/docs/sources/as-code/observability-as-code/provision-resources/intro-git-sync.md index 4d9bda529a3..9e32ce32e8a 100644 --- a/docs/sources/as-code/observability-as-code/provision-resources/intro-git-sync.md +++ b/docs/sources/as-code/observability-as-code/provision-resources/intro-git-sync.md @@ -127,7 +127,13 @@ An instance can be in one of the following Git Sync states: ## Common use cases -You can use Git Sync in the following scenarios. +{{< admonition type="note" >}} + +Refer to [Git Sync deployment scenarios](https://grafana.com/docs/grafana//as-code/observability-as-code/provision-resources/git-sync-deployment-scenarios) for sample scenarios, including architecture and configuration details. + +{{< /admonition >}} + +You can use Git Sync for the following use cases: ### Version control and auditing diff --git a/docs/sources/as-code/observability-as-code/provision-resources/use-git-sync.md b/docs/sources/as-code/observability-as-code/provision-resources/use-git-sync.md index f7d37d4b94e..11ce2aee0aa 100644 --- a/docs/sources/as-code/observability-as-code/provision-resources/use-git-sync.md +++ b/docs/sources/as-code/observability-as-code/provision-resources/use-git-sync.md @@ -14,7 +14,7 @@ labels: - cloud title: Manage provisioned repositories with Git Sync menuTitle: Manage repositories with Git Sync -weight: 120 +weight: 400 canonical: https://grafana.com/docs/grafana/latest/as-code/observability-as-code/provision-resources/use-git-sync/ aliases: - ../../../observability-as-code/provision-resources/use-git-sync/ # /docs/grafana/next/observability-as-code/provision-resources/use-git-sync/ From eab5d2b30ef443870167a7ec34ba3d2b603a8aef Mon Sep 17 00:00:00 2001 From: Bogdan Matei Date: Fri, 12 Dec 2025 17:17:34 +0200 Subject: [PATCH 070/297] Dashboard: Fix rogue modal when exiting edit mode (#115240) * Dashboard: Fix rogue modal when exiting edit mode * Remove unnecessary change --- .../dashboard-scene/saving/DashboardSceneChangeTracker.ts | 4 ++++ public/app/features/dashboard-scene/scene/DashboardScene.tsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts b/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts index 6bae04e7248..3884106df39 100644 --- a/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts +++ b/public/app/features/dashboard-scene/saving/DashboardSceneChangeTracker.ts @@ -206,6 +206,10 @@ export class DashboardSceneChangeTracker { } this._changesWorker!.onmessage = (e: MessageEvent) => { + if (!this._dashboard.state.isEditing) { + return; + } + this.updateIsDirty(!!e.data.hasChanges); }; diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 2312fdec7ed..4e3fa954a51 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -271,7 +271,7 @@ export class DashboardScene extends SceneObjectBase impleme public onEnterEditMode = () => { // Save this state - this._initialState = sceneUtils.cloneSceneObjectState(this.state); + this._initialState = sceneUtils.cloneSceneObjectState(this.state, { isDirty: false }); this._initialUrlState = locationService.getLocation(); // Switch to edit mode From 5f80a29a289bf7251010496e1b22192d79185319 Mon Sep 17 00:00:00 2001 From: William Wernert Date: Fri, 12 Dec 2025 10:25:08 -0500 Subject: [PATCH 071/297] Alerting: Prevent users from saving rules to git-synced folders (#114944) --------- Co-authored-by: Yuri Tseretyan --- pkg/services/ngalert/api/api_ruler.go | 13 ++- pkg/services/ngalert/api/api_ruler_test.go | 61 +++++++++++ .../ngalert/api/prometheus/api_prometheus.go | 2 +- pkg/services/ngalert/eval/eval.go | 2 +- pkg/services/ngalert/models/alert_rule.go | 15 +++ .../ngalert/provisioning/alert_rules.go | 19 +++- .../ngalert/provisioning/alert_rules_test.go | 103 ++++++++++++++++++ pkg/services/ngalert/state/historian/loki.go | 2 +- 8 files changed, 209 insertions(+), 8 deletions(-) diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index d81f448f600..9fde7a2ec6f 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -412,11 +412,16 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *contextmodel.ReqContext, ruleGro deletePermanently = true } - namespace, err := srv.store.GetNamespaceByUID(c.Req.Context(), namespaceUID, c.GetOrgID(), c.SignedInUser) + f, err := srv.store.GetNamespaceByUID(c.Req.Context(), namespaceUID, c.GetOrgID(), c.SignedInUser) if err != nil { return toNamespaceErrorResponse(err) } + namespace := ngmodels.NewNamespace(f) + if err := namespace.ValidateForRuleStorage(); err != nil { + return ErrResp(http.StatusBadRequest, fmt.Errorf("%w: %s", ngmodels.ErrAlertRuleFailedValidation, err), "") + } + if err := srv.checkGroupLimits(ruleGroupConfig); err != nil { return ErrResp(http.StatusBadRequest, err, "") } @@ -841,10 +846,14 @@ func (srv RulerSrv) RouteUpdateNamespaceRules(c *contextmodel.ReqContext, body a return ErrResp(http.StatusBadRequest, errors.New("missing request body"), "") } - namespace, err := srv.store.GetNamespaceByUID(c.Req.Context(), namespaceUID, c.GetOrgID(), c.SignedInUser) + f, err := srv.store.GetNamespaceByUID(c.Req.Context(), namespaceUID, c.GetOrgID(), c.SignedInUser) if err != nil { return toNamespaceErrorResponse(err) } + namespace := ngmodels.NewNamespace(f) + if err := namespace.ValidateForRuleStorage(); err != nil { + return ErrResp(http.StatusBadRequest, fmt.Errorf("%w: %s", ngmodels.ErrAlertRuleFailedValidation, err), "") + } ruleGroups, _, err := srv.searchAuthorizedAlertRules(c.Req.Context(), authorizedRuleGroupQuery{ User: c.SignedInUser, diff --git a/pkg/services/ngalert/api/api_ruler_test.go b/pkg/services/ngalert/api/api_ruler_test.go index c6ddeee2a6c..e09088e4669 100644 --- a/pkg/services/ngalert/api/api_ruler_test.go +++ b/pkg/services/ngalert/api/api_ruler_test.go @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/infra/log" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" @@ -1288,4 +1289,64 @@ func TestRouteUpdateNamespaceRules(t *testing.T) { updatedRules := getRecordedUpdatedRules(ruleStore) require.Empty(t, updatedRules) }) + + t.Run("should reject update when folder is managed by ManagerKindRepo", func(t *testing.T) { + ruleStore := fakes.NewRuleStore(t) + provisioningStore := fakes.NewFakeProvisioningStore() + + // Create a managed folder + managedFolder := randFolder() + managedFolder.ManagedBy = utils.ManagerKindRepo + ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], managedFolder) + + // Create some rules in the managed folder + ruleGen := models.RuleGen.With( + models.RuleGen.WithOrgID(orgID), + models.RuleGen.WithNamespaceUID(managedFolder.UID), + ) + rules := ruleGen.GenerateManyRef(2) + ruleStore.PutRule(context.Background(), rules...) + + permissions := createPermissionsForRules(rules, orgID) + requestCtx := createRequestContextWithPerms(orgID, permissions, nil) + + svc := createServiceWithProvenanceStore(ruleStore, provisioningStore) + response := svc.RouteUpdateNamespaceRules(requestCtx, apimodels.UpdateNamespaceRulesRequest{ + IsPaused: util.Pointer(true), + }, managedFolder.UID) + + require.Equal(t, http.StatusBadRequest, response.Status()) + require.Contains(t, string(response.Body()), "cannot store rules in folder managed by Git Sync") + + // Verify no rules were updated + updatedRules := getRecordedUpdatedRules(ruleStore) + require.Empty(t, updatedRules) + }) +} + +func TestRoutePostNameRulesConfig(t *testing.T) { + t.Run("should reject creation when folder is managed by ManagerKindRepo", func(t *testing.T) { + orgID := rand.Int63() + ruleStore := fakes.NewRuleStore(t) + + // Create a managed folder + managedFolder := randFolder() + managedFolder.ManagedBy = utils.ManagerKindRepo + ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], managedFolder) + + permissions := map[int64]map[string][]string{ + orgID: { + dashboards.ScopeFoldersProvider.GetResourceScopeUID(managedFolder.UID): {dashboards.ActionFoldersRead}, + }, + } + requestCtx := createRequestContextWithPerms(orgID, permissions, nil) + + svc := createService(ruleStore, nil) + response := svc.RoutePostNameRulesConfig(requestCtx, apimodels.PostableRuleGroupConfig{ + Name: "test-group", + }, managedFolder.UID) + + require.Equal(t, http.StatusBadRequest, response.Status()) + require.Contains(t, string(response.Body()), "cannot store rules in folder managed by Git Sync") + }) } diff --git a/pkg/services/ngalert/api/prometheus/api_prometheus.go b/pkg/services/ngalert/api/prometheus/api_prometheus.go index 412a0795469..36457cf7a30 100644 --- a/pkg/services/ngalert/api/prometheus/api_prometheus.go +++ b/pkg/services/ngalert/api/prometheus/api_prometheus.go @@ -296,7 +296,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon allowedNamespaces := map[string]string{} for namespaceUID, folder := range namespaceMap { // only add namespaces that the user has access to rules in - hasAccess, err := srv.authz.HasAccessInFolder(c.Req.Context(), c.SignedInUser, ngmodels.Namespace(*folder.ToFolderReference())) + hasAccess, err := srv.authz.HasAccessInFolder(c.Req.Context(), c.SignedInUser, ngmodels.NewNamespace(folder)) if err != nil { ruleResponse.Status = "error" ruleResponse.Error = fmt.Sprintf("failed to get namespaces visible to the user: %s", err.Error()) diff --git a/pkg/services/ngalert/eval/eval.go b/pkg/services/ngalert/eval/eval.go index 687e9b2dd03..40e2a257e60 100644 --- a/pkg/services/ngalert/eval/eval.go +++ b/pkg/services/ngalert/eval/eval.go @@ -204,7 +204,7 @@ func IsNonRetryableError(err error) bool { return false } -// HasErrors returns true when Results contains at least one element and all elements are errors +// IsError returns true when Results contains at least one element and all elements are errors func (evalResults Results) IsError() bool { for _, r := range evalResults { if r.State != Error { diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index 7ee223e8165..bc21e31cb45 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -24,6 +24,7 @@ import ( alertingModels "github.com/grafana/alerting/models" + "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/setting" @@ -397,6 +398,20 @@ type Namespaced interface { type Namespace folder.FolderReference +func NewNamespace(f *folder.Folder) Namespace { + return Namespace(*f.ToFolderReference()) +} + +func (n Namespace) ValidateForRuleStorage() error { + if n.UID == "" { + return fmt.Errorf("cannot store rules in folder without UID") + } + if n.ManagedBy == utils.ManagerKindRepo { + return fmt.Errorf("cannot store rules in folder managed by Git Sync") + } + return nil +} + func (n Namespace) GetNamespaceUID() string { return n.UID } diff --git a/pkg/services/ngalert/provisioning/alert_rules.go b/pkg/services/ngalert/provisioning/alert_rules.go index aa6c4c55b34..b5ff7506650 100644 --- a/pkg/services/ngalert/provisioning/alert_rules.go +++ b/pkg/services/ngalert/provisioning/alert_rules.go @@ -114,7 +114,7 @@ func (service *AlertRuleService) ListAlertRules(ctx context.Context, user identi } folderUIDs := make([]string, 0, len(folders)) for _, f := range folders { - access, err := service.authz.HasAccessInFolder(ctx, user, models.Namespace(*f.ToFolderReference())) + access, err := service.authz.HasAccessInFolder(ctx, user, models.NewNamespace(f)) if err != nil { return nil, nil, "", err } @@ -407,6 +407,9 @@ func (service *AlertRuleService) UpdateRuleGroup(ctx context.Context, user ident if err := models.ValidateRuleGroupInterval(intervalSeconds, service.baseIntervalSeconds); err != nil { return err } + if err := service.ensureNamespace(ctx, user, user.GetOrgID(), namespaceUID); err != nil { + return err + } return service.xact.InTransaction(ctx, func(ctx context.Context) error { query := &models.ListAlertRulesQuery{ OrgID: user.GetOrgID(), @@ -471,6 +474,10 @@ func (service *AlertRuleService) ReplaceRuleGroup(ctx context.Context, user iden return err } + if err := service.ensureNamespace(ctx, user, user.GetOrgID(), group.FolderUID); err != nil { + return err + } + // If the rule group is reserved for no-group rules, we cannot have multiple rules in it. if models.IsNoGroupRuleGroup(group.Title) && len(group.Rules) > 1 { return fmt.Errorf("rule group %s is reserved for no-group rules and cannot be used for rule groups with multiple rules", group.Title) @@ -1025,6 +1032,7 @@ func (service *AlertRuleService) checkGroupLimits(group models.AlertRuleGroup) e // ensureNamespace ensures that the rule has a valid namespace UID. // If the rule does not have a namespace UID or the namespace (folder) does not exist it will return an error. +// If the folder is managed by a manager, it will also return an error. func (service *AlertRuleService) ensureNamespace(ctx context.Context, user identity.Requester, orgID int64, namespaceUID string) error { if namespaceUID == "" { return fmt.Errorf("%w: folderUID must be set", models.ErrAlertRuleFailedValidation) @@ -1037,18 +1045,23 @@ func (service *AlertRuleService) ensureNamespace(ctx context.Context, user ident } // ensure the namespace exists - _, err := service.folderService.Get(ctx, &folder.GetFolderQuery{ + f, err := service.folderService.Get(ctx, &folder.GetFolderQuery{ OrgID: orgID, UID: &namespaceUID, SignedInUser: user, }) - if err != nil { + if err != nil || f == nil { if errors.Is(err, dashboards.ErrFolderNotFound) { return fmt.Errorf("%w: folder does not exist", models.ErrAlertRuleFailedValidation) } return err } + // check if the folder is managed by a manager + if err := models.NewNamespace(f).ValidateForRuleStorage(); err != nil { + return fmt.Errorf("%w: %s", models.ErrAlertRuleFailedValidation, err) + } + return nil } diff --git a/pkg/services/ngalert/provisioning/alert_rules_test.go b/pkg/services/ngalert/provisioning/alert_rules_test.go index c552f797429..ed001a527c4 100644 --- a/pkg/services/ngalert/provisioning/alert_rules_test.go +++ b/pkg/services/ngalert/provisioning/alert_rules_test.go @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/infra/db" @@ -867,6 +868,27 @@ func TestIntegrationAlertRuleService(t *testing.T) { require.NoError(t, err) require.Equal(t, int64(120), rule.IntervalSeconds) }) + + t.Run("UpdateRuleGroup should reject when folder is managed by a manager", func(t *testing.T) { + service, _, _, ac := initService(t) + ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) { + return true, nil + } + + managedFolderUID := "managed-folder-update-group" + fs := foldertest.NewFakeService() + fs.AddFolder(&folder.Folder{ + OrgID: orgID, + UID: managedFolderUID, + Title: "Managed Folder", + ManagedBy: utils.ManagerKindRepo, + }) + service.folderService = fs + + err := service.UpdateRuleGroup(context.Background(), u, managedFolderUID, "some-group", 120) + require.ErrorIs(t, err, models.ErrAlertRuleFailedValidation) + require.ErrorContains(t, err, "cannot store rules in folder managed by Git Sync") + }) } func TestIntegrationCreateAlertRule(t *testing.T) { @@ -1166,6 +1188,30 @@ func TestIntegrationCreateAlertRule(t *testing.T) { require.NoError(t, err) require.True(t, models.IsNoGroupRuleGroup(retrievedRule.RuleGroup), "Rule should be considered NoGroup rule") }) + + t.Run("should reject creation when folder is managed by a manager", func(t *testing.T) { + service, _, _, ac := initService(t) + ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) { + return true, nil + } + + managedFolderUID := "managed-folder" + fs := foldertest.NewFakeService() + fs.AddFolder(&folder.Folder{ + OrgID: orgID, + UID: managedFolderUID, + Title: "Managed Folder", + ManagedBy: utils.ManagerKindRepo, + }) + service.folderService = fs + + rule := dummyRule("test-managed-folder", orgID) + rule.NamespaceUID = managedFolderUID + + _, err := service.CreateAlertRule(context.Background(), u, rule, models.ProvenanceNone) + require.ErrorIs(t, err, models.ErrAlertRuleFailedValidation) + require.ErrorContains(t, err, "cannot store rules in folder managed by Git Sync") + }) } func TestUpdateAlertRule(t *testing.T) { @@ -1316,6 +1362,36 @@ func TestUpdateAlertRule(t *testing.T) { require.Equal(t, "nogroup-update-new", updated.Title) require.Equal(t, originalInterval, updated.IntervalSeconds) }) + + t.Run("should reject update when folder is managed by a manager", func(t *testing.T) { + service, ruleStore, provenanceStore, ac := initService(t) + ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) { + return true, nil + } + + managedFolderUID := "managed-folder-update" + fs := foldertest.NewFakeService() + fs.AddFolder(&folder.Folder{ + OrgID: orgID, + UID: managedFolderUID, + Title: "Managed Folder", + ManagedBy: utils.ManagerKindRepo, + }) + service.folderService = fs + + // Create an existing rule + existingRule := dummyRule("test-managed-folder-update", orgID) + existingRule.NamespaceUID = managedFolderUID + _, err := ruleStore.InsertAlertRules(context.Background(), models.NewUserUID(u), []models.InsertRule{{AlertRule: existingRule}}) + require.NoError(t, err) + require.NoError(t, provenanceStore.SetProvenance(context.Background(), &existingRule, orgID, models.ProvenanceNone)) + + // Try to update the rule + existingRule.Title = "Updated Title" + _, err = service.UpdateAlertRule(context.Background(), u, existingRule, models.ProvenanceNone) + require.ErrorIs(t, err, models.ErrAlertRuleFailedValidation) + require.ErrorContains(t, err, "cannot store rules in folder managed by Git Sync") + }) } func TestDeleteAlertRule(t *testing.T) { @@ -2054,6 +2130,33 @@ func TestReplaceGroup(t *testing.T) { require.Error(t, err) require.ErrorContains(t, err, "cannot move rule out of this group") }) + + t.Run("should reject replace when folder is managed by a manager", func(t *testing.T) { + service, _, _, ac := initService(t) + ac.CanWriteAllRulesFunc = func(ctx context.Context, user identity.Requester) (bool, error) { + return true, nil + } + + managedFolderUID := "managed-folder-replace" + fs := foldertest.NewFakeService() + fs.AddFolder(&folder.Folder{ + OrgID: orgID, + UID: managedFolderUID, + Title: "Managed Folder", + ManagedBy: utils.ManagerKindRepo, + }) + service.folderService = fs + + group := models.AlertRuleGroup{ + Title: "test-group", + FolderUID: managedFolderUID, + Interval: 60, + } + + err := service.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceNone, "") + require.ErrorIs(t, err, models.ErrAlertRuleFailedValidation) + require.ErrorContains(t, err, "cannot store rules in folder managed by Git Sync") + }) } func TestDeleteRuleGroup(t *testing.T) { diff --git a/pkg/services/ngalert/state/historian/loki.go b/pkg/services/ngalert/state/historian/loki.go index 137c18f031c..e07461f27bf 100644 --- a/pkg/services/ngalert/state/historian/loki.go +++ b/pkg/services/ngalert/state/historian/loki.go @@ -530,7 +530,7 @@ func (h *RemoteLokiBackend) getFolderUIDsForFilter(ctx context.Context, query mo uids := make([]string, 0, len(folders)) // now keep only UIDs of folder in which user can read rules. for _, f := range folders { - hasAccess, err := h.ac.HasAccessInFolder(ctx, query.SignedInUser, models.Namespace(*f.ToFolderReference())) + hasAccess, err := h.ac.HasAccessInFolder(ctx, query.SignedInUser, models.NewNamespace(f)) if err != nil { return nil, err } From 584615cf3fd36b6b4b05c28f2c477486c2b903f6 Mon Sep 17 00:00:00 2001 From: Yunwen Zheng Date: Fri, 12 Dec 2025 10:32:05 -0500 Subject: [PATCH 072/297] RecentlyViewedDashboards: Set up container on browsing dashboards page (#115164) * RecentlyViewedDashboards: Set up container on browsing dashboards page --- .../BrowseDashboardsPage.tsx | 3 + .../components/RecentlyViewedDashboards.tsx | 77 +++++++++++++++++++ .../browse-dashboards/components/utils.ts | 36 +++++++++ .../actions/dashboardActions.ts | 17 +--- public/locales/en-US/grafana.json | 4 + 5 files changed, 122 insertions(+), 15 deletions(-) create mode 100644 public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx diff --git a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx index 921c67ba9cb..e177fcf9fa2 100644 --- a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx +++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx @@ -27,6 +27,7 @@ import { BrowseFilters } from './components/BrowseFilters'; import { BrowseView } from './components/BrowseView'; import CreateNewButton from './components/CreateNewButton'; import { FolderActionsButton } from './components/FolderActionsButton'; +import { RecentlyViewedDashboards } from './components/RecentlyViewedDashboards'; import { SearchView } from './components/SearchView'; import { getFolderPermissions } from './permissions'; import { useHasSelection } from './state/hooks'; @@ -178,6 +179,8 @@ const BrowseDashboardsPage = memo(({ queryParams }: { queryParams: Record + {/* only show recently viewed dashboards when in root */} + {!folderUID && }
{ + if (!evaluateBooleanFlag('recentlyViewedDashboards', false)) { + return []; + } + return getRecentlyViewedDashboards(MAX_RECENT); + }, []); + + if (!evaluateBooleanFlag('recentlyViewedDashboards', false)) { + return null; + } + + return ( + + Recently viewed + + } + isOpen={true} + className={styles.title} + contentClassName={styles.content} + > + {/* placeholder */} + {loading && } + {/* TODO: Better empty state https://github.com/grafana/grafana/issues/114804 */} + {!loading && recentDashboards.length === 0 && ( + {t('browse-dashboards.recently-viewed.empty', 'Nothing viewed yet')} + )} + + {/* TODO: implement actual card content */} + {!loading && recentDashboards.length > 0 && ( + <> + {recentDashboards.map((dash) => ( +
+ {dash.name} +
+ ))} + + )} +
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => { + const accent = theme.visualization.getColorByName('purple'); // or your own hex + + return { + title: css({ + background: `linear-gradient(90deg, ${accent} 0%, #e478eaff 100%)`, + WebkitTextFillColor: 'transparent', + backgroundClip: 'text', + color: 'transparent', + '& button svg': { + color: accent, + }, + }), + content: css({ + paddingTop: theme.spacing(0), + }), + }; +}; diff --git a/public/app/features/browse-dashboards/components/utils.ts b/public/app/features/browse-dashboards/components/utils.ts index a6c97e4788f..e1a3d5b5a02 100644 --- a/public/app/features/browse-dashboards/components/utils.ts +++ b/public/app/features/browse-dashboards/components/utils.ts @@ -1,6 +1,9 @@ import { config } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; +import impressionSrv from 'app/core/services/impression_srv'; import { ResourceRef } from 'app/features/provisioning/components/BulkActions/useBulkActionJob'; +import { getGrafanaSearcher } from 'app/features/search/service/searcher'; +import { DashboardQueryResult } from 'app/features/search/service/types'; import { DashboardTreeSelection, DashboardViewItemWithUIItems, BrowseDashboardsPermissions } from '../types'; @@ -60,3 +63,36 @@ export function canSelectItems(permissions: BrowseDashboardsPermissions) { const canSelectDashboards = canEditDashboards || canDeleteDashboards; return Boolean(canSelectFolders || canSelectDashboards); } + +/** + * Returns dashboard search results ordered the same way the user opened them. + */ +export async function getRecentlyViewedDashboards(maxItems = 5): Promise { + try { + const recentlyOpened = (await impressionSrv.getDashboardOpened()).slice(0, maxItems); + if (!recentlyOpened.length) { + return []; + } + + const searchResults = await getGrafanaSearcher().search({ + kind: ['dashboard'], + limit: recentlyOpened.length, + uid: recentlyOpened, + }); + + const dashboards = searchResults.view.toArray(); + // Keep dashboards in the same order the user opened them. + // When a UID is missing from the search response + // push it to the end instead of letting indexOf return -1 + const order = (uid: string) => { + const idx = recentlyOpened.indexOf(uid); + return idx === -1 ? recentlyOpened.length : idx; + }; + + dashboards.sort((a, b) => order(a.uid) - order(b.uid)); + return dashboards; + } catch (error) { + console.error('Failed to load recently viewed dashboards', error); + return []; + } +} diff --git a/public/app/features/commandPalette/actions/dashboardActions.ts b/public/app/features/commandPalette/actions/dashboardActions.ts index 70a62e57295..ac3e8ce83dd 100644 --- a/public/app/features/commandPalette/actions/dashboardActions.ts +++ b/public/app/features/commandPalette/actions/dashboardActions.ts @@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from 'react'; import { t } from '@grafana/i18n'; import { config } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; -import impressionSrv from 'app/core/services/impression_srv'; +import { getRecentlyViewedDashboards } from 'app/features/browse-dashboards/components/utils'; import { getGrafanaSearcher } from 'app/features/search/service/searcher'; import { CommandPaletteAction } from '../types'; @@ -20,20 +20,7 @@ export async function getRecentDashboardActions(): Promise { - const orderA = recentUids.indexOf(resultA.uid); - const orderB = recentUids.indexOf(resultB.uid); - return orderA - orderB; - }); + const recentResults = await getRecentlyViewedDashboards(MAX_RECENT_DASHBOARDS); const recentDashboardActions: CommandPaletteAction[] = recentResults.map((item) => { const { url, name } = item; // items are backed by DataFrameView, so must hold the url in a closure diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 9d9ec201d18..167f3714495 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -3707,6 +3707,10 @@ "clear": "Clear search and filters", "text": "No results found for your query" }, + "recently-viewed": { + "empty": "Nothing viewed yet", + "title": "Recently viewed" + }, "restore": { "all-failed_one": "Failed to restore {{count}} dashboard", "all-failed_other": "Failed to restore {{count}} dashboards", From b40d0e6ff4c3b02a9c8be9b75282f2c2e2b1fe35 Mon Sep 17 00:00:00 2001 From: Kristina Demeshchik Date: Fri, 12 Dec 2025 10:36:31 -0500 Subject: [PATCH 073/297] Dashboards: Fix accessible color palettes not being saved in v2 schema (#115244) * Fix palette color v2 conversion * v2->v1 conversion --- .../serialization/transformToV2TypesUtils.ts | 10 ++++++++++ .../app/features/dashboard/api/ResponseTransformers.ts | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts b/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts index fe1d85e6525..8b17ab8ff89 100644 --- a/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts +++ b/public/app/features/dashboard-scene/serialization/transformToV2TypesUtils.ts @@ -119,6 +119,16 @@ export function colorIdEnumToColorIdV2(colorId: FieldColorModeIdV1 | string): Fi return 'continuous-greens'; case FieldColorModeIdV1.ContinuousPurples: return 'continuous-purples'; + case FieldColorModeIdV1.ContinuousViridis: + return 'continuous-viridis'; + case FieldColorModeIdV1.ContinuousMagma: + return 'continuous-magma'; + case FieldColorModeIdV1.ContinuousPlasma: + return 'continuous-plasma'; + case FieldColorModeIdV1.ContinuousInferno: + return 'continuous-inferno'; + case FieldColorModeIdV1.ContinuousCividis: + return 'continuous-cividis'; case FieldColorModeIdV1.Fixed: return 'fixed'; case FieldColorModeIdV1.Shades: diff --git a/public/app/features/dashboard/api/ResponseTransformers.ts b/public/app/features/dashboard/api/ResponseTransformers.ts index b6fcba03de3..d2640991b1e 100644 --- a/public/app/features/dashboard/api/ResponseTransformers.ts +++ b/public/app/features/dashboard/api/ResponseTransformers.ts @@ -1268,6 +1268,16 @@ function colorIdToEnumv1(colorId: FieldColorModeId): FieldColorModeIdV1 { return FieldColorModeIdV1.ContinuousGreens; case 'continuous-purples': return FieldColorModeIdV1.ContinuousPurples; + case 'continuous-viridis': + return FieldColorModeIdV1.ContinuousViridis; + case 'continuous-magma': + return FieldColorModeIdV1.ContinuousMagma; + case 'continuous-plasma': + return FieldColorModeIdV1.ContinuousPlasma; + case 'continuous-inferno': + return FieldColorModeIdV1.ContinuousInferno; + case 'continuous-cividis': + return FieldColorModeIdV1.ContinuousCividis; case 'fixed': return FieldColorModeIdV1.Fixed; case 'shades': From 7114b9cd3bafa4d108081c8d95ac0ed6e31928f6 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Fri, 12 Dec 2025 16:56:23 +0100 Subject: [PATCH 074/297] Log Line Details: Fix width calculation in dashboards (#115248) * FieldSelector: rename functions to be more explicit * LogDetailsContext: calculate width based on field selector visibility * LogLineDetails: Fix sidebar max width calculation * Update functions usage * Add regression and fix context calculation --- .../features/explore/Logs/LogsTableWrap.tsx | 4 +-- .../fieldSelector/FieldSelector.tsx | 10 +++--- .../components/panel/LogDetailsContext.tsx | 36 +++++++++++++------ .../components/panel/LogLineDetails.test.tsx | 26 ++++++++++++++ .../logs/components/panel/LogLineDetails.tsx | 10 ++++-- .../logs/components/panel/LogList.tsx | 2 ++ .../logs/components/panel/LogListContext.tsx | 4 +-- 7 files changed, 70 insertions(+), 22 deletions(-) diff --git a/public/app/features/explore/Logs/LogsTableWrap.tsx b/public/app/features/explore/Logs/LogsTableWrap.tsx index ff8cceaadcf..cde546d92ba 100644 --- a/public/app/features/explore/Logs/LogsTableWrap.tsx +++ b/public/app/features/explore/Logs/LogsTableWrap.tsx @@ -19,7 +19,7 @@ import { t } from '@grafana/i18n'; import { reportInteraction } from '@grafana/runtime'; import { getDragStyles, InlineField, Select, useStyles2 } from '@grafana/ui'; import { - getSidebarWidth, + getFieldSelectorWidth, LogsTableFieldSelector, MIN_WIDTH, } from 'app/features/logs/components/fieldSelector/FieldSelector'; @@ -279,7 +279,7 @@ export function LogsTableWrap(props: Props) { // The panel state is updated when the user interacts with the multi-select sidebar }, [currentDataFrame, getColumnsFromProps]); - const [sidebarWidth, setSidebarWidth] = useState(getSidebarWidth(SETTING_KEY_ROOT)); + const [sidebarWidth, setSidebarWidth] = useState(getFieldSelectorWidth(SETTING_KEY_ROOT)); const tableWidth = props.width - sidebarWidth; const styles = useStyles2(getStyles, height, sidebarWidth); diff --git a/public/app/features/logs/components/fieldSelector/FieldSelector.tsx b/public/app/features/logs/components/fieldSelector/FieldSelector.tsx index 44b1ff0f85c..d68cf5340c9 100644 --- a/public/app/features/logs/components/fieldSelector/FieldSelector.tsx +++ b/public/app/features/logs/components/fieldSelector/FieldSelector.tsx @@ -35,7 +35,7 @@ export const LogListFieldSelector = ({ containerElement, dataFrames, logs }: Log const { displayedFields, onClickShowField, onClickHideField, setDisplayedFields, logOptionsStorageKey } = useLogListContext(); const [sidebarHeight, setSidebarHeight] = useState(220); - const [sidebarWidth, setSidebarWidth] = useState(getSidebarWidth(logOptionsStorageKey)); + const [sidebarWidth, setSidebarWidth] = useState(getFieldSelectorWidth(logOptionsStorageKey)); const dragStyles = useStyles2(getDragStyles); useLayoutEffect(() => { @@ -74,7 +74,7 @@ export const LogListFieldSelector = ({ containerElement, dataFrames, logs }: Log }, [setSidebarWidthWrapper]); const expand = useCallback(() => { - const width = getSidebarWidth(logOptionsStorageKey); + const width = getFieldSelectorWidth(logOptionsStorageKey); setSidebarWidthWrapper(width < 2 * MIN_WIDTH ? DEFAULT_WIDTH : width); reportInteraction('logs_field_selector_expand_clicked', { mode: 'logs', @@ -205,7 +205,7 @@ export const LogsTableFieldSelector = ({ }, [setSidebarWidthWrapper]); const expand = useCallback(() => { - const width = getSidebarWidth(SETTING_KEY_ROOT); + const width = getFieldSelectorWidth(SETTING_KEY_ROOT); setSidebarWidthWrapper(width < 2 * MIN_WIDTH ? DEFAULT_WIDTH : width); reportInteraction('logs_field_selector_expand_clicked', { mode: 'table', @@ -436,7 +436,7 @@ function getSuggestedFields(logs: LogListModel[], displayedFields: string[], def return suggestedFields; } -export function getSidebarWidth(logOptionsStorageKey?: string): number { +export function getFieldSelectorWidth(logOptionsStorageKey?: string): number { const width = (logOptionsStorageKey ? parseInt(store.get(`${logOptionsStorageKey}.fieldSelector.width`) ?? DEFAULT_WIDTH, 10) @@ -445,7 +445,7 @@ export function getSidebarWidth(logOptionsStorageKey?: string): number { return width < MIN_WIDTH ? MIN_WIDTH : width; } -export function getSidebarState(logOptionsStorageKey?: string): boolean | undefined { +export function getFieldSelectorState(logOptionsStorageKey?: string): boolean | undefined { if (!logOptionsStorageKey) { return undefined; } diff --git a/public/app/features/logs/components/panel/LogDetailsContext.tsx b/public/app/features/logs/components/panel/LogDetailsContext.tsx index de0c044911e..d3526e2bcb4 100644 --- a/public/app/features/logs/components/panel/LogDetailsContext.tsx +++ b/public/app/features/logs/components/panel/LogDetailsContext.tsx @@ -3,7 +3,7 @@ import { createContext, ReactNode, useCallback, useContext, useEffect, useState import { LogRowModel, store } from '@grafana/data'; -import { getSidebarWidth } from '../fieldSelector/FieldSelector'; +import { getFieldSelectorWidth } from '../fieldSelector/FieldSelector'; import { LogLineDetailsMode } from './LogLineDetails'; import { LogListModel } from './processing'; @@ -56,6 +56,7 @@ export interface Props { logs: LogRowModel[]; logOptionsStorageKey?: string; showControls: boolean; + showFieldSelector?: boolean; } export const LogDetailsContextProvider = ({ @@ -68,12 +69,13 @@ export const LogDetailsContextProvider = ({ : getDefaultDetailsMode(containerElement), logs, showControls, + showFieldSelector, }: Props) => { const [showDetails, setShowDetails] = useState([]); const [currentLog, setCurrentLog] = useState(undefined); const [detailsWidth, setDetailsWidthState] = useState( - getDetailsWidth(containerElement, logOptionsStorageKey, undefined, detailsModeProp, showControls) + getDetailsWidth(containerElement, logOptionsStorageKey, undefined, detailsModeProp, showControls, showFieldSelector) ); const [detailsMode, setDetailsMode] = useState( detailsModeProp ?? getDefaultDetailsMode(containerElement) @@ -101,8 +103,10 @@ export const LogDetailsContextProvider = ({ // Sync log details inline and sidebar width useEffect(() => { - setDetailsWidthState(getDetailsWidth(containerElement, logOptionsStorageKey, undefined, detailsMode, showControls)); - }, [containerElement, detailsMode, logOptionsStorageKey, showControls]); + setDetailsWidthState( + getDetailsWidth(containerElement, logOptionsStorageKey, undefined, detailsMode, showControls, showFieldSelector) + ); + }, [containerElement, detailsMode, logOptionsStorageKey, showControls, showFieldSelector]); // Sync log details width useEffect(() => { @@ -111,13 +115,20 @@ export const LogDetailsContextProvider = ({ } const handleResize = debounce(() => { setDetailsWidthState((detailsWidth) => - getDetailsWidth(containerElement, logOptionsStorageKey, detailsWidth, detailsMode, showControls) + getDetailsWidth( + containerElement, + logOptionsStorageKey, + detailsWidth, + detailsMode, + showControls, + showFieldSelector + ) ); }, 50); const observer = new ResizeObserver(() => handleResize()); observer.observe(containerElement); return () => observer.disconnect(); - }, [containerElement, detailsMode, logOptionsStorageKey, showControls, showDetails]); + }, [containerElement, detailsMode, logOptionsStorageKey, showControls, showDetails, showFieldSelector]); const closeDetails = useCallback(() => { showDetails.forEach((log) => removeDetailsScrollPosition(log)); @@ -158,7 +169,10 @@ export const LogDetailsContextProvider = ({ return; } - const maxWidth = containerElement.clientWidth - getSidebarWidth(logOptionsStorageKey) - LOG_LIST_MIN_WIDTH; + const maxWidth = + containerElement.clientWidth - + (showFieldSelector ? getFieldSelectorWidth(logOptionsStorageKey) : 0) - + LOG_LIST_MIN_WIDTH; if (width > maxWidth) { return; } @@ -166,7 +180,7 @@ export const LogDetailsContextProvider = ({ store.set(`${logOptionsStorageKey}.detailsWidth`, width); setDetailsWidthState(width); }, - [containerElement, logOptionsStorageKey] + [containerElement, logOptionsStorageKey, showFieldSelector] ); return ( @@ -196,12 +210,14 @@ export function getDetailsWidth( logOptionsStorageKey?: string, currentWidth?: number, detailsMode: LogLineDetailsMode = 'sidebar', - showControls?: boolean + showControls?: boolean, + showFieldSelector?: boolean ) { if (!containerElement) { return 0; } - const availableWidth = containerElement.clientWidth - getSidebarWidth(logOptionsStorageKey); + const availableWidth = + containerElement.clientWidth - (showFieldSelector ? getFieldSelectorWidth(logOptionsStorageKey) : 0); if (detailsMode === 'inline') { return availableWidth - getScrollbarWidth() - (showControls ? LOG_LIST_CONTROLS_WIDTH : 0); } diff --git a/public/app/features/logs/components/panel/LogLineDetails.test.tsx b/public/app/features/logs/components/panel/LogLineDetails.test.tsx index dafc055ce0d..cee12e10d8b 100644 --- a/public/app/features/logs/components/panel/LogLineDetails.test.tsx +++ b/public/app/features/logs/components/panel/LogLineDetails.test.tsx @@ -20,6 +20,7 @@ import { setPluginLinksHook } from '@grafana/runtime'; import { createTempoDatasource } from 'app/plugins/datasource/tempo/test/mocks'; import { LOG_LINE_BODY_FIELD_NAME } from '../LogDetailsBody'; +import { getFieldSelectorWidth } from '../fieldSelector/FieldSelector'; import { createLogLine } from '../mocks/logRow'; import { emptyContextData, LogDetailsContext, LogDetailsContextData } from './LogDetailsContext'; @@ -27,6 +28,10 @@ import { LogLineDetails, Props } from './LogLineDetails'; import { LogListContext, LogListContextData } from './LogListContext'; import { defaultValue } from './__mocks__/LogListContext'; +jest.mock('../fieldSelector/FieldSelector'); + +jest.mocked(getFieldSelectorWidth).mockReturnValue(220); + jest.mock('@grafana/assistant', () => { return { ...jest.requireActual('@grafana/assistant'), @@ -79,6 +84,7 @@ const setup = ( }, timeZone: 'browser', showControls: true, + showFieldSelector: true, ...(propOverrides || {}), }; @@ -775,4 +781,24 @@ describe('LogLineDetails', () => { expect(screen.getByText('value')).toBeInTheDocument(); expect(screen.getByText('Open service overview for label')).toBeInTheDocument(); }); + + describe('Width regressions', () => { + test('should consider Fields Selector width when enabled', () => { + jest.mocked(getFieldSelectorWidth).mockClear(); + + setup({ showFieldSelector: true }, { labels: { key1: 'label1', key2: 'label2' } }); + expect(screen.getByText('Log line')).toBeInTheDocument(); + expect(screen.getByText('Fields')).toBeInTheDocument(); + expect(getFieldSelectorWidth).toHaveBeenCalled(); + }); + + test('should not consider Fields Selector width when disabled', () => { + jest.mocked(getFieldSelectorWidth).mockClear(); + + setup({ showFieldSelector: false }, { labels: { key1: 'label1', key2: 'label2' } }); + expect(screen.getByText('Log line')).toBeInTheDocument(); + expect(screen.getByText('Fields')).toBeInTheDocument(); + expect(getFieldSelectorWidth).not.toHaveBeenCalled(); + }); + }); }); diff --git a/public/app/features/logs/components/panel/LogLineDetails.tsx b/public/app/features/logs/components/panel/LogLineDetails.tsx index 19639e61f8c..bc5a961cbd7 100644 --- a/public/app/features/logs/components/panel/LogLineDetails.tsx +++ b/public/app/features/logs/components/panel/LogLineDetails.tsx @@ -7,7 +7,7 @@ import { t } from '@grafana/i18n'; import { reportInteraction } from '@grafana/runtime'; import { getDragStyles, Icon, Tab, TabsBar, useStyles2 } from '@grafana/ui'; -import { getSidebarWidth } from '../fieldSelector/FieldSelector'; +import { getFieldSelectorWidth } from '../fieldSelector/FieldSelector'; import { getDetailsScrollPosition, saveDetailsScrollPosition, useLogDetailsContext } from './LogDetailsContext'; import { LogLineDetailsComponent } from './LogLineDetailsComponent'; @@ -22,12 +22,13 @@ export interface Props { timeRange: TimeRange; timeZone: string; showControls: boolean; + showFieldSelector: boolean | undefined; } export type LogLineDetailsMode = 'inline' | 'sidebar'; export const LogLineDetails = memo( - ({ containerElement, focusLogLine, logs, timeRange, timeZone, showControls }: Props) => { + ({ containerElement, focusLogLine, logs, timeRange, timeZone, showControls, showFieldSelector }: Props) => { const { noInteractions, logOptionsStorageKey } = useLogListContext(); const { detailsWidth, setDetailsWidth } = useLogDetailsContext(); const styles = useStyles2(getStyles, 'sidebar', showControls); @@ -48,7 +49,10 @@ export const LogLineDetails = memo( } }, [noInteractions]); - const maxWidth = containerElement.clientWidth - getSidebarWidth(logOptionsStorageKey) - LOG_LIST_MIN_WIDTH; + const maxWidth = + containerElement.clientWidth - + (showFieldSelector ? getFieldSelectorWidth(logOptionsStorageKey) : 0) - + LOG_LIST_MIN_WIDTH; return ( )}
diff --git a/public/app/features/logs/components/panel/LogListContext.tsx b/public/app/features/logs/components/panel/LogListContext.tsx index a917b0eac56..139f40ea98e 100644 --- a/public/app/features/logs/components/panel/LogListContext.tsx +++ b/public/app/features/logs/components/panel/LogListContext.tsx @@ -27,7 +27,7 @@ import { config, getDataSourceSrv } from '@grafana/runtime'; import { PopoverContent } from '@grafana/ui'; import { checkLogsError, checkLogsSampled, downloadLogs as download, DownloadFormat } from '../../utils'; -import { getSidebarState } from '../fieldSelector/FieldSelector'; +import { getFieldSelectorState } from '../fieldSelector/FieldSelector'; import { getDisplayedFieldsForLogs } from '../otel/formats'; import { getDefaultDetailsMode, getDetailsWidth } from './LogDetailsContext'; @@ -245,7 +245,7 @@ export const LogListContextProvider = ({ dedupStrategy, fontSize, forceEscape: logListState.forceEscape, - fieldSelectorOpen: getSidebarState(logOptionsStorageKey), + fieldSelectorOpen: getFieldSelectorState(logOptionsStorageKey), showTime, showUniqueLabels, syntaxHighlighting, From c0dc92e8cd23dbdbc5fba6bc6554b9c416882a9d Mon Sep 17 00:00:00 2001 From: Paul Marbach Date: Fri, 12 Dec 2025 11:10:56 -0500 Subject: [PATCH 075/297] Gauge: Fit-and-finish tweaks to glows, text position, and sparkline size (#115173) * Gauge: Fit-and-finish tweaks to glows, text position, and sparkline size * adjust text height and positions a little more * cohesive no data handling * more tweaks * fix migration test * Fix JSON formatting by adding missing newline * remove new line --- .../panel-gauge/gauge_tests_new.v42.json | 153 ++++++++++- .../panel-gauge/gauge_tests_new.json | 255 +++++++++++++----- .../components/RadialGauge/RadialGauge.tsx | 12 +- .../RadialGauge/RadialSparkline.tsx | 29 +- .../src/components/RadialGauge/RadialText.tsx | 49 ++-- .../src/components/RadialGauge/effects.tsx | 9 +- .../panel/radialbar/RadialBarPanel.tsx | 6 + 7 files changed, 389 insertions(+), 124 deletions(-) diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_new.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_new.v42.json index c589f8b7400..9f8cf76c9f7 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_new.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_new.v42.json @@ -1603,7 +1603,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -1671,7 +1670,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "hide": false, "max": 98, "min": 5, "noise": 22, @@ -1689,7 +1687,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -1757,7 +1754,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "hide": false, "max": 98, "min": 5, "noise": 22, @@ -1788,7 +1784,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -1857,7 +1852,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "hide": false, "max": 8, "min": 1, "noise": 2, @@ -1875,7 +1869,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -1944,7 +1937,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "hide": false, "max": 12, "min": 1, "noise": 2, @@ -1962,7 +1954,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -2030,7 +2021,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "hide": false, "max": 100, "min": 10, "noise": 22, @@ -2048,7 +2038,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -2116,7 +2105,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "hide": false, "max": 100, "min": 10, "noise": 22, @@ -2129,6 +2117,147 @@ ], "title": "Backend", "type": "radialbar" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 66 + }, + "id": 35, + "panels": [], + "title": "Empty data", + "type": "row" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 67 + }, + "id": 36, + "options": { + "barWidthFactor": 0.5, + "effects": { + "barGlow": false, + "centerGlow": false, + "gradient": true, + "rounded": false, + "spotlight": false + }, + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "segmentCount": 1, + "segmentSpacing": 0.3, + "shape": "gauge", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sparkline": true + }, + "pluginVersion": "13.0.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 0 + } + ], + "title": "Numeric, no series", + "type": "gauge" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 67 + }, + "id": 37, + "options": { + "barWidthFactor": 0.5, + "effects": { + "barGlow": false, + "centerGlow": false, + "gradient": true, + "rounded": false, + "spotlight": false + }, + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "segmentCount": 1, + "segmentSpacing": 0.3, + "shape": "gauge", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sparkline": true + }, + "pluginVersion": "13.0.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "logs" + } + ], + "title": "Non-numeric", + "type": "gauge" } ], "preload": false, diff --git a/devenv/dev-dashboards/panel-gauge/gauge_tests_new.json b/devenv/dev-dashboards/panel-gauge/gauge_tests_new.json index f9ee5a8c4e3..b3c47c9aa7a 100644 --- a/devenv/dev-dashboards/panel-gauge/gauge_tests_new.json +++ b/devenv/dev-dashboards/panel-gauge/gauge_tests_new.json @@ -75,9 +75,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": true, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -152,9 +152,9 @@ "effects": { "barGlow": false, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -229,9 +229,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -306,9 +306,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -383,9 +383,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -460,9 +460,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": false, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -537,9 +537,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": false, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -627,9 +627,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -704,9 +704,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -781,9 +781,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -858,9 +858,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": false, "rounded": true, - "spotlight": true, - "gradient": false + "spotlight": true }, "orientation": "auto", "reduceOptions": { @@ -952,9 +952,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1029,9 +1029,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1106,9 +1106,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1183,9 +1183,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1260,9 +1260,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": false, "rounded": false, - "spotlight": false, - "gradient": false + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1354,9 +1354,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1435,9 +1435,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1516,9 +1516,9 @@ "effects": { "barGlow": false, "centerGlow": false, + "gradient": true, "rounded": false, - "spotlight": false, - "gradient": true + "spotlight": false }, "orientation": "auto", "reduceOptions": { @@ -1565,7 +1565,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -1606,9 +1605,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -1631,7 +1630,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "hide": false, "max": 98, "min": 5, "noise": 22, @@ -1649,7 +1647,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -1690,9 +1687,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -1715,7 +1712,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "hide": false, "max": 98, "min": 5, "noise": 22, @@ -1746,7 +1742,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -1788,9 +1783,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -1813,7 +1808,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "hide": false, "max": 8, "min": 1, "noise": 2, @@ -1831,7 +1825,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -1873,9 +1866,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -1898,7 +1891,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "hide": false, "max": 12, "min": 1, "noise": 2, @@ -1916,7 +1908,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -1957,9 +1948,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -1982,7 +1973,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "hide": false, "max": 100, "min": 10, "noise": 22, @@ -2000,7 +1990,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "description": "", "fieldConfig": { "defaults": { "color": { @@ -2041,9 +2030,9 @@ "effects": { "barGlow": true, "centerGlow": true, + "gradient": true, "rounded": true, - "spotlight": true, - "gradient": true + "spotlight": true }, "glow": "both", "orientation": "auto", @@ -2066,7 +2055,6 @@ "datasource": { "type": "grafana-testdata-datasource" }, - "hide": false, "max": 100, "min": 10, "noise": 22, @@ -2079,6 +2067,147 @@ ], "title": "Backend", "type": "radialbar" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 66 + }, + "id": 35, + "panels": [], + "title": "Empty data", + "type": "row" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 67 + }, + "id": 36, + "options": { + "barWidthFactor": 0.5, + "effects": { + "barGlow": false, + "centerGlow": false, + "gradient": true, + "rounded": false, + "spotlight": false + }, + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "segmentCount": 1, + "segmentSpacing": 0.3, + "shape": "gauge", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sparkline": true + }, + "pluginVersion": "13.0.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 0 + } + ], + "title": "Numeric, no series", + "type": "gauge" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 67 + }, + "id": 37, + "options": { + "barWidthFactor": 0.5, + "effects": { + "barGlow": false, + "centerGlow": false, + "gradient": true, + "rounded": false, + "spotlight": false + }, + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "segmentCount": 1, + "segmentSpacing": 0.3, + "shape": "gauge", + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sparkline": true + }, + "pluginVersion": "13.0.0-pre", + "targets": [ + { + "refId": "A", + "scenarioId": "logs" + } + ], + "title": "Non-numeric", + "type": "gauge" } ], "preload": false, @@ -2095,5 +2224,5 @@ "timezone": "browser", "title": "Panel tests - Gauge (new)", "uid": "panel-tests-gauge-new", - "version": 6 + "version": 9 } diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx index 18147e0cac5..fadabf8ec72 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx @@ -106,6 +106,11 @@ export function RadialGauge(props: RadialGaugeProps) { const gaugeId = useId(); const styles = useStyles2(getStyles); + let effectiveTextMode = textMode; + if (effectiveTextMode === 'auto') { + effectiveTextMode = vizCount === 1 ? 'value' : 'value_and_name'; + } + const startAngle = shape === 'gauge' ? 250 : 0; const endAngle = shape === 'gauge' ? 110 : 360; @@ -188,7 +193,7 @@ export function RadialGauge(props: RadialGaugeProps) { // These elements are only added for first value / bar if (barIndex === 0) { if (glowBar) { - defs.push(); + defs.push(); } if (glowCenter) { @@ -198,14 +203,14 @@ export function RadialGauge(props: RadialGaugeProps) { graphics.push( ); @@ -254,6 +259,7 @@ export function RadialGauge(props: RadialGaugeProps) { theme={theme} color={color} shape={shape} + textMode={effectiveTextMode} /> ); } diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialSparkline.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialSparkline.tsx index 7a25fe3201a..acb255a3f3e 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialSparkline.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialSparkline.tsx @@ -1,11 +1,9 @@ -import { css } from '@emotion/css'; - import { FieldDisplay, GrafanaTheme2, FieldConfig } from '@grafana/data'; import { GraphFieldConfig, GraphGradientMode, LineInterpolation } from '@grafana/schema'; import { Sparkline } from '../Sparkline/Sparkline'; -import { RadialShape } from './RadialGauge'; +import { RadialShape, RadialTextMode } from './RadialGauge'; import { GaugeDimensions } from './utils'; interface RadialSparklineProps { @@ -14,23 +12,22 @@ interface RadialSparklineProps { theme: GrafanaTheme2; color?: string; shape?: RadialShape; + textMode: Exclude; } -export function RadialSparkline({ sparkline, dimensions, theme, color, shape }: RadialSparklineProps) { +export function RadialSparkline({ sparkline, dimensions, theme, color, shape, textMode }: RadialSparklineProps) { + const { radius, barWidth } = dimensions; + if (!sparkline) { return null; } - const { radius, barWidth } = dimensions; - - const height = radius / 4; - const widthFactor = shape === 'gauge' ? 1.6 : 1.4; - const width = radius * widthFactor - barWidth; - const topPos = shape === 'gauge' ? `${dimensions.gaugeBottomY - height}px` : `calc(50% + ${radius / 2.8}px)`; - - const styles = css({ - position: 'absolute', - top: topPos, - }); + const showNameAndValue = textMode === 'value_and_name'; + const height = radius / (showNameAndValue ? 4 : 3); + const width = radius * (shape === 'gauge' ? 1.6 : 1.4) - barWidth; + const topPos = + shape === 'gauge' + ? `${dimensions.gaugeBottomY - height}px` + : `calc(50% + ${radius / (showNameAndValue ? 3.3 : 4)}px)`; const config: FieldConfig = { color: { @@ -45,7 +42,7 @@ export function RadialSparkline({ sparkline, dimensions, theme, color, shape }: }; return ( -
+
); diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialText.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialText.tsx index d01a2d99570..51a1c64c842 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialText.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialText.tsx @@ -1,6 +1,12 @@ import { css } from '@emotion/css'; -import { DisplayValue, DisplayValueAlignmentFactors, formattedValueToString, GrafanaTheme2 } from '@grafana/data'; +import { + DisplayValue, + DisplayValueAlignmentFactors, + FieldSparkline, + formattedValueToString, + GrafanaTheme2, +} from '@grafana/data'; import { useStyles2 } from '../../themes/ThemeContext'; import { calculateFontSize } from '../../utils/measureText'; @@ -8,21 +14,13 @@ import { calculateFontSize } from '../../utils/measureText'; import { RadialShape, RadialTextMode } from './RadialGauge'; import { GaugeDimensions } from './utils'; -// function toCartesian(centerX: number, centerY: number, radius: number, angleInDegrees: number) { -// let radian = ((angleInDegrees - 90) * Math.PI) / 180.0; -// return { -// x: centerX + radius * Math.cos(radian), -// y: centerY + radius * Math.sin(radian), -// }; -// } - interface RadialTextProps { displayValue: DisplayValue; theme: GrafanaTheme2; dimensions: GaugeDimensions; - textMode: RadialTextMode; - vizCount: number; + textMode: Exclude; shape: RadialShape; + sparkline?: FieldSparkline; alignmentFactors?: DisplayValueAlignmentFactors; valueManualFontSize?: number; nameManualFontSize?: number; @@ -33,8 +31,8 @@ export function RadialText({ theme, dimensions, textMode, - vizCount, shape, + sparkline, alignmentFactors, valueManualFontSize, nameManualFontSize, @@ -46,10 +44,6 @@ export function RadialText({ return null; } - if (textMode === 'auto') { - textMode = vizCount === 1 ? 'value' : 'value_and_name'; - } - const nameToAlignTo = (alignmentFactors ? alignmentFactors.title : displayValue.title) ?? ''; const valueToAlignTo = formattedValueToString(alignmentFactors ? alignmentFactors : displayValue); @@ -59,7 +53,7 @@ export function RadialText({ // Not sure where this comes from but svg text is not using body line-height const lineHeight = 1.21; - const valueWidthToRadiusFactor = 0.85; + const valueWidthToRadiusFactor = 0.82; const nameToHeightFactor = 0.45; const largeRadiusScalingDecay = 0.86; @@ -98,18 +92,23 @@ export function RadialText({ const valueHeight = valueFontSize * lineHeight; const nameHeight = nameFontSize * lineHeight; - const valueY = showName ? centerY - nameHeight / 2 : centerY; - const valueNameSpacing = valueHeight / 3.5; - const nameY = showValue ? valueY + valueHeight / 2 + valueNameSpacing : centerY; + const valueY = showName ? centerY - nameHeight * 0.3 : centerY; + const nameY = showValue ? valueY + valueHeight * 0.7 : centerY; const nameColor = showValue ? theme.colors.text.secondary : theme.colors.text.primary; const suffixShift = (valueFontSize - unitFontSize * 1.2) / 2; - // For gauge shape we shift text up a bit - const valueDy = shape === 'gauge' ? -valueFontSize * 0.3 : 0; - const nameDy = shape === 'gauge' ? -nameFontSize * 0.7 : 0; + // adjust the text up on gauges and when sparklines are present + let yOffset = 0; + if (shape === 'gauge') { + // we render from the center of the gauge, so move up by half of half of the total height + yOffset -= (valueHeight + nameHeight) / 4; + } + if (sparkline) { + yOffset -= 8; + } return ( - + {showValue && ( {displayValue.prefix ?? ''} {displayValue.text} @@ -133,7 +131,6 @@ export function RadialText({ fontSize={nameFontSize} x={centerX} y={nameY} - dy={nameDy} textAnchor="middle" dominantBaseline="middle" fill={nameColor} diff --git a/packages/grafana-ui/src/components/RadialGauge/effects.tsx b/packages/grafana-ui/src/components/RadialGauge/effects.tsx index 551d9d91186..354a68a25ba 100644 --- a/packages/grafana-ui/src/components/RadialGauge/effects.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/effects.tsx @@ -4,11 +4,12 @@ import { GaugeDimensions } from './utils'; export interface GlowGradientProps { id: string; - radius: number; + barWidth: number; } -export function GlowGradient({ id, radius }: GlowGradientProps) { - const glowSize = 0.02 * radius; +export function GlowGradient({ id, barWidth }: GlowGradientProps) { + // 0.75 is the minimum glow size, and it scales with bar width + const glowSize = 0.75 + barWidth * 0.08; return ( @@ -82,7 +83,7 @@ export function MiddleCircleGlow({ dimensions, gaugeId, color }: CenterGlowProps <> - + diff --git a/public/app/plugins/panel/radialbar/RadialBarPanel.tsx b/public/app/plugins/panel/radialbar/RadialBarPanel.tsx index fa064037157..86235a3bf68 100644 --- a/public/app/plugins/panel/radialbar/RadialBarPanel.tsx +++ b/public/app/plugins/panel/radialbar/RadialBarPanel.tsx @@ -7,6 +7,7 @@ import { getFieldDisplayValues, PanelProps, } from '@grafana/data'; +import { PanelDataErrorView } from '@grafana/runtime'; import { DataLinksContextMenu, Stack, VizRepeater, VizRepeaterRenderValueProps } from '@grafana/ui'; import { DataLinksContextMenuApi, RadialGauge } from '@grafana/ui/internal'; import { config } from 'app/core/config'; @@ -14,6 +15,7 @@ import { config } from 'app/core/config'; import { Options } from './panelcfg.gen'; export function RadialBarPanel({ + id, height, width, data, @@ -88,6 +90,10 @@ export function RadialBarPanel({ const minVizHeight = 60; const minVizWidth = 60; + if (getValues()[0]?.display?.text === 'No data') { + return ; + } + return ( Date: Fri, 12 Dec 2025 17:24:36 +0100 Subject: [PATCH 076/297] LogLineContext: remove broken permalink prop (#115252) --- public/app/features/explore/Logs/Logs.tsx | 1 - public/app/plugins/panel/logs/LogsPanel.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/public/app/features/explore/Logs/Logs.tsx b/public/app/features/explore/Logs/Logs.tsx index 84470d73a61..1ff012f3fee 100644 --- a/public/app/features/explore/Logs/Logs.tsx +++ b/public/app/features/explore/Logs/Logs.tsx @@ -789,7 +789,6 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => { logOptionsStorageKey={SETTING_KEY_ROOT} timeZone={timeZone} displayedFields={displayedFields} - onPermalinkClick={onPermalinkClick} onClickShowField={showField} onClickHideField={hideField} /> diff --git a/public/app/plugins/panel/logs/LogsPanel.tsx b/public/app/plugins/panel/logs/LogsPanel.tsx index 3cf47744762..3d89ba1d105 100644 --- a/public/app/plugins/panel/logs/LogsPanel.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.tsx @@ -566,7 +566,6 @@ export const LogsPanel = ({ logLineMenuCustomItems={isLogLineMenuCustomItems(logLineMenuCustomItems) ? logLineMenuCustomItems : undefined} timeZone={timeZone} displayedFields={displayedFields} - onPermalinkClick={showPermaLink() ? onPermalinkClick : undefined} onClickShowField={showField} onClickHideField={hideField} /> From 1b59c82b747d002399983221a44cad42816e18d1 Mon Sep 17 00:00:00 2001 From: Will Assis <35489495+gassiss@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:00:08 -0500 Subject: [PATCH 077/297] Revert "Unified-storage: sql backend key path backfill (#115033)" (#115257) This reverts commit b2dd095bd860cfbc9187e0fdf91cae18206044e4. --- .../unified/sql/db/migrations/resource_mig.go | 140 ------------------ 1 file changed, 140 deletions(-) diff --git a/pkg/storage/unified/sql/db/migrations/resource_mig.go b/pkg/storage/unified/sql/db/migrations/resource_mig.go index 170b418a22a..fbbfe32d4a6 100644 --- a/pkg/storage/unified/sql/db/migrations/resource_mig.go +++ b/pkg/storage/unified/sql/db/migrations/resource_mig.go @@ -2,11 +2,8 @@ package migrations import ( "fmt" - "strings" - "github.com/bwmarrin/snowflake" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" - "github.com/grafana/grafana/pkg/util/xorm" ) func initResourceTables(mg *migrator.Migrator) string { @@ -207,142 +204,5 @@ func initResourceTables(mg *migrator.Migrator) string { Name: "IDX_resource_history_key_path", })) - mg.AddMigration("resource_history key_path backfill", &ResourceHistoryKeyPathBackfillMigration{}) - return marker } - -type ResourceHistoryKeyPathBackfillMigration struct { - migrator.MigrationBase -} - -func (m *ResourceHistoryKeyPathBackfillMigration) SQL(_ migrator.Dialect) string { - return "resource_history key_path backfill code migration" -} - -func (m *ResourceHistoryKeyPathBackfillMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) error { - rows, err := getResourceHistoryRows(sess, mg, resourceHistoryRow{}) - if err != nil { - return err - } - - for len(rows) > 0 { - if err := updateResourceHistoryKeyPath(sess, rows); err != nil { - return err - } - - rows, err = getResourceHistoryRows(sess, mg, rows[len(rows)-1]) - if err != nil { - return err - } - } - - return nil -} - -func updateResourceHistoryKeyPath(sess *xorm.Session, rows []resourceHistoryRow) error { - if len(rows) == 0 { - return nil - } - - updates := []resourceHistoryRow{} - - for _, row := range rows { - if row.KeyPath == "" { - row.KeyPath = parseKeyPath(row) - updates = append(updates, row) - } - } - - if len(updates) == 0 { - return nil - } - - guids := "" - setCases := "CASE" - for _, row := range updates { - guids += fmt.Sprintf("'%s',", row.GUID) - setCases += fmt.Sprintf(" WHEN guid = '%s' THEN '%s'", row.GUID, row.KeyPath) - } - - guids = strings.TrimRight(guids, ",") - setCases += " ELSE key_path END " - - // the query will look like this - // UPDATE resource_history - // SET key_path = CASE - // WHEN guid = '1402de51-669b-4206-8a6c-005a00eee6e3' then 'unified/data/folder.grafana.app/folders/default/cf6lylpvls000c/1998492888241012800~created~' - // WHEN guid = '8842cc56-f22b-45e1-82b1-99759cd443b3' then 'unified/data/dashboard.grafana.app/dashboards/default/adzvfhp/1998492902577144677~created~cf6lylpvls000c' - // ELSE key_path END - // WHERE guid IN ('1402de51-669b-4206-8a6c-005a00eee6e3', '8842cc56-f22b-45e1-82b1-99759cd443b3') - // AND key_path = ''; - sql := fmt.Sprintf(` - UPDATE resource_history - SET key_path = %s - WHERE guid IN (%s) - AND key_path = ''; - `, setCases, guids) - - if _, err := sess.Exec(sql); err != nil { - return err - } - - return nil -} - -func parseKeyPath(row resourceHistoryRow) string { - var action string - switch row.Action { - case 1: - action = "created" - case 2: - action = "updated" - case 3: - action = "deleted" - } - return fmt.Sprintf("unified/data/%s/%s/%s/%s/%d~%s~%s", row.Group, row.Resource, row.Namespace, row.Name, snowflakeFromRv(row.ResourceVersion), action, row.Folder) -} - -func snowflakeFromRv(rv int64) int64 { - return (((rv / 1000) - snowflake.Epoch) << (snowflake.NodeBits + snowflake.StepBits)) + (rv % 1000) -} - -type resourceHistoryRow struct { - GUID string `xorm:"guid"` - Group string `xorm:"group"` - Resource string `xorm:"resource"` - Namespace string `xorm:"namespace"` - Name string `xorm:"name"` - ResourceVersion int64 `xorm:"resource_version"` - Action int64 `xorm:"action"` - Folder string `xorm:"folder"` - KeyPath string `xorm:"key_path"` -} - -func getResourceHistoryRows(sess *xorm.Session, mg *migrator.Migrator, continueRow resourceHistoryRow) ([]resourceHistoryRow, error) { - var rows []resourceHistoryRow - cols := fmt.Sprintf( - "%s, %s, %s, %s, %s, %s, %s, %s, %s", - mg.Dialect.Quote("guid"), - mg.Dialect.Quote("group"), - mg.Dialect.Quote("resource"), - mg.Dialect.Quote("namespace"), - mg.Dialect.Quote("name"), - mg.Dialect.Quote("resource_version"), - mg.Dialect.Quote("action"), - mg.Dialect.Quote("folder"), - mg.Dialect.Quote("key_path")) - sql := fmt.Sprintf(` - SELECT %s - FROM resource_history - WHERE (resource_version > %d OR (resource_version = %d AND guid > '%s')) - AND key_path = '' - ORDER BY resource_version ASC, guid ASC - LIMIT 1000; - `, cols, continueRow.ResourceVersion, continueRow.ResourceVersion, continueRow.GUID) - if err := sess.SQL(sql).Find(&rows); err != nil { - return nil, err - } - - return rows, nil -} From 629570926d8846f68715ed6240413452055c5c97 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Fri, 12 Dec 2025 18:05:10 +0100 Subject: [PATCH 078/297] Zanzana: Fix resource translation for dashboards (#115077) --- pkg/services/authz/zanzana/common/info.go | 32 ++++++- pkg/services/authz/zanzana/common/tuple.go | 3 + .../authz/zanzana/common/tuple_test.go | 89 +++++++++++++++++++ .../authz/zanzana/server/server_check_test.go | 12 +++ .../authz/zanzana/server/server_test.go | 2 + 5 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 pkg/services/authz/zanzana/common/tuple_test.go diff --git a/pkg/services/authz/zanzana/common/info.go b/pkg/services/authz/zanzana/common/info.go index c17970ca1b3..4e4bbedc0b7 100644 --- a/pkg/services/authz/zanzana/common/info.go +++ b/pkg/services/authz/zanzana/common/info.go @@ -4,8 +4,12 @@ import ( "google.golang.org/protobuf/types/known/structpb" authzv1 "github.com/grafana/authlib/authz/proto/v1" + + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/services/accesscontrol" authzextv1 "github.com/grafana/grafana/pkg/services/authz/proto/v1" ) @@ -44,7 +48,8 @@ func getTypeInfo(group, resource string) (typeInfo, bool) { func NewResourceInfoFromCheck(r *authzv1.CheckRequest) ResourceInfo { typ, relations := getTypeAndRelations(r.GetGroup(), r.GetResource()) - return newResource( + + resource := newResource( typ, r.GetGroup(), r.GetResource(), @@ -53,6 +58,19 @@ func NewResourceInfoFromCheck(r *authzv1.CheckRequest) ResourceInfo { r.GetSubresource(), relations, ) + + // Special case for creating folders and resources in the root folder + if r.GetVerb() == utils.VerbCreate { + if resource.IsFolderResource() && resource.name == "" { + resource.name = accesscontrol.GeneralFolderUID + } else if resource.HasFolderSupport() && resource.folder == "" { + resource.folder = accesscontrol.GeneralFolderUID + } + + return resource + } + + return resource } func NewResourceInfoFromBatchItem(i *authzextv1.BatchCheckItem) ResourceInfo { @@ -164,3 +182,15 @@ func (r ResourceInfo) IsValidRelation(relation string) bool { func (r ResourceInfo) HasSubresource() bool { return r.subresource != "" } + +var resourcesWithFolderSupport = map[string]bool{ + dashboardV1.DashboardResourceInfo.GroupResource().Group: true, +} + +func (r ResourceInfo) HasFolderSupport() bool { + return resourcesWithFolderSupport[r.group] +} + +func (r ResourceInfo) IsFolderResource() bool { + return r.group == folders.FolderResourceInfo.GroupResource().Group +} diff --git a/pkg/services/authz/zanzana/common/tuple.go b/pkg/services/authz/zanzana/common/tuple.go index b1b6499dcd2..7f0faff1f18 100644 --- a/pkg/services/authz/zanzana/common/tuple.go +++ b/pkg/services/authz/zanzana/common/tuple.go @@ -228,6 +228,9 @@ func TranslateToResourceTuple(subject string, action, kind, name string) (*openf } if name == "*" { + if m.group != "" && m.resource != "" { + return NewGroupResourceTuple(subject, m.relation, m.group, m.resource, m.subresource), true + } return NewGroupResourceTuple(subject, m.relation, translation.group, translation.resource, m.subresource), true } diff --git a/pkg/services/authz/zanzana/common/tuple_test.go b/pkg/services/authz/zanzana/common/tuple_test.go new file mode 100644 index 00000000000..ecb4d6e9dd1 --- /dev/null +++ b/pkg/services/authz/zanzana/common/tuple_test.go @@ -0,0 +1,89 @@ +package common + +import ( + "testing" + + openfgav1 "github.com/openfga/api/proto/openfga/v1" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/structpb" +) + +type translationTestCase struct { + testName string + subject string + action string + kind string + name string + expected *openfgav1.TupleKey +} + +func TestTranslateToResourceTuple(t *testing.T) { + tests := []translationTestCase{ + { + testName: "dashboards:read in folders", + subject: "user:1", + action: "dashboards:read", + kind: "folders", + name: "*", + expected: &openfgav1.TupleKey{ + User: "user:1", + Relation: "get", + Object: "group_resource:dashboard.grafana.app/dashboards", + }, + }, + { + testName: "dashboards:read for all dashboards", + subject: "user:1", + action: "dashboards:read", + kind: "dashboards", + name: "*", + expected: &openfgav1.TupleKey{ + User: "user:1", + Relation: "get", + Object: "group_resource:dashboard.grafana.app/dashboards", + }, + }, + { + testName: "dashboards:read for general folder", + subject: "user:1", + action: "dashboards:read", + kind: "folders", + name: "general", + expected: &openfgav1.TupleKey{ + User: "user:1", + Relation: "resource_get", + Object: "folder:general", + Condition: &openfgav1.RelationshipCondition{ + Name: "subresource_filter", + Context: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "subresources": structpb.NewListValue(&structpb.ListValue{ + Values: []*structpb.Value{structpb.NewStringValue("dashboard.grafana.app/dashboards")}, + }), + }, + }, + }, + }, + }, + { + testName: "folders:read", + subject: "user:1", + action: "folders:read", + kind: "folders", + name: "*", + expected: &openfgav1.TupleKey{ + User: "user:1", + Relation: "get", + Object: "group_resource:folder.grafana.app/folders", + }, + }, + } + + for _, test := range tests { + t.Run(test.testName, func(t *testing.T) { + tuple, ok := TranslateToResourceTuple(test.subject, test.action, test.kind, test.name) + require.True(t, ok) + require.EqualExportedValues(t, test.expected, tuple) + }) + } +} diff --git a/pkg/services/authz/zanzana/server/server_check_test.go b/pkg/services/authz/zanzana/server/server_check_test.go index 59a192fe6a0..d8e8fa01526 100644 --- a/pkg/services/authz/zanzana/server/server_check_test.go +++ b/pkg/services/authz/zanzana/server/server_check_test.go @@ -212,4 +212,16 @@ func testCheck(t *testing.T, server *Server) { require.NoError(t, err) assert.True(t, res.GetAllowed(), "user should be able to view dashboards in folder 6") }) + + t.Run("user:18 should be able to create folder in root folder", func(t *testing.T) { + res, err := server.Check(newContextWithNamespace(), newReq("user:18", utils.VerbCreate, folderGroup, folderResource, "", "", "")) + require.NoError(t, err) + assert.Equal(t, true, res.GetAllowed()) + }) + + t.Run("user:18 should be able to create dashboard in root folder", func(t *testing.T) { + res, err := server.Check(newContextWithNamespace(), newReq("user:18", utils.VerbCreate, dashboardGroup, dashboardResource, "", "", "")) + require.NoError(t, err) + assert.Equal(t, true, res.GetAllowed()) + }) } diff --git a/pkg/services/authz/zanzana/server/server_test.go b/pkg/services/authz/zanzana/server/server_test.go index 63cf8ee2a50..3f3a7e2cad6 100644 --- a/pkg/services/authz/zanzana/server/server_test.go +++ b/pkg/services/authz/zanzana/server/server_test.go @@ -71,6 +71,8 @@ func setup(t *testing.T, srv *Server) *Server { common.NewTypedResourceTuple("user:15", common.RelationGet, common.TypeUser, userGroup, userResource, statusSubresource, "1"), common.NewTypedResourceTuple("user:16", common.RelationGet, common.TypeServiceAccount, serviceAccountGroup, serviceAccountResource, statusSubresource, "1"), common.NewFolderTuple("user:17", common.RelationSetView, "4"), + common.NewFolderTuple("user:18", common.RelationCreate, "general"), + common.NewFolderResourceTuple("user:18", common.RelationCreate, dashboardGroup, dashboardResource, "", "general"), } return setupOpenFGADatabase(t, srv, tuples) From 644f7b7001fb30f06a998cc0e2a16778a74a717b Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Fri, 12 Dec 2025 18:59:49 +0100 Subject: [PATCH 079/297] Infinite scroll: Fix interaction with client-side filter (#115243) Infinite scroll: fix interaction with client-side filter --- .../app/features/logs/components/panel/InfiniteScroll.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/public/app/features/logs/components/panel/InfiniteScroll.tsx b/public/app/features/logs/components/panel/InfiniteScroll.tsx index fd5fb15a014..4508682df57 100644 --- a/public/app/features/logs/components/panel/InfiniteScroll.tsx +++ b/public/app/features/logs/components/panel/InfiniteScroll.tsx @@ -256,7 +256,11 @@ export const InfiniteScroll = ({ if (props.visibleStartIndex === 0) { noScrollRef.current = scrollElement.scrollHeight <= scrollElement.clientHeight; } - if (noScrollRef.current || infiniteLoaderState === 'loading' || infiniteLoaderState === 'out-of-bounds') { + if (noScrollRef.current) { + setInfiniteLoaderState('idle'); + return; + } + if (infiniteLoaderState === 'loading' || infiniteLoaderState === 'out-of-bounds') { return; } const lastLogIndex = logs.length - 1; @@ -267,7 +271,7 @@ export const InfiniteScroll = ({ setInfiniteLoaderState('idle'); } }, - [infiniteLoaderState, logs.length, scrollElement] + [infiniteLoaderState, logs, scrollElement] ); const getItemKey = useCallback((index: number) => (logs[index] ? logs[index].uid : index.toString()), [logs]); From b29e8ccb457f53dc920132f432d7b8c2a30db5e4 Mon Sep 17 00:00:00 2001 From: Kristina Demeshchik Date: Fri, 12 Dec 2025 13:14:56 -0500 Subject: [PATCH 080/297] Dashboards: Generate default tab title when converting rows with empty titles to tabs (#115256) Generate default title for empty row titles --- .../layout-tabs/TabsLayoutManager.test.tsx | 53 +++++++++++++++++++ .../scene/layout-tabs/TabsLayoutManager.tsx | 10 +++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.test.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.test.tsx index 3d1fd828042..0502f0d62e8 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.test.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.test.tsx @@ -262,4 +262,57 @@ describe('TabsLayoutManager', () => { expect(manager.getVizPanels().length).toBe(1); }); }); + + describe('createFromLayout', () => { + it('should convert rows with titles to tabs', () => { + const rowsLayout = new RowsLayoutManager({ + rows: [new RowItem({ title: 'Row 1' }), new RowItem({ title: 'Row 2' })], + }); + + const tabsManager = TabsLayoutManager.createFromLayout(rowsLayout); + + expect(tabsManager.state.tabs).toHaveLength(2); + expect(tabsManager.state.tabs[0].state.title).toBe('Row 1'); + expect(tabsManager.state.tabs[1].state.title).toBe('Row 2'); + }); + + it('should use default title when row has empty title', () => { + const rowsLayout = new RowsLayoutManager({ + rows: [new RowItem({ title: '' })], + }); + + const tabsManager = TabsLayoutManager.createFromLayout(rowsLayout); + + expect(tabsManager.state.tabs).toHaveLength(1); + expect(tabsManager.state.tabs[0].state.title).toBe('New tab'); + }); + + it('should generate unique titles for multiple rows with empty titles', () => { + const rowsLayout = new RowsLayoutManager({ + rows: [new RowItem({ title: '' }), new RowItem({ title: '' }), new RowItem({ title: '' })], + }); + + const tabsManager = TabsLayoutManager.createFromLayout(rowsLayout); + + expect(tabsManager.state.tabs).toHaveLength(3); + expect(tabsManager.state.tabs[0].state.title).toBe('New tab'); + expect(tabsManager.state.tabs[1].state.title).toBe('New tab 1'); + expect(tabsManager.state.tabs[2].state.title).toBe('New tab 2'); + }); + + it('should generate unique titles when mixing empty and existing titles', () => { + const rowsLayout = new RowsLayoutManager({ + rows: [ + new RowItem({ title: 'New row' }), // existing title that matches default + new RowItem({ title: '' }), // empty, should get unique title + ], + }); + + const tabsManager = TabsLayoutManager.createFromLayout(rowsLayout); + + expect(tabsManager.state.tabs).toHaveLength(2); + expect(tabsManager.state.tabs[0].state.title).toBe('New row'); + expect(tabsManager.state.tabs[1].state.title).toBe('New tab'); + }); + }); }); diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx index 79d027b1a02..4e08b56e54f 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx @@ -410,6 +410,10 @@ export class TabsLayoutManager extends SceneObjectBase i let tabs: TabItem[] = []; if (layout instanceof RowsLayoutManager) { + const existingNames = new Set( + layout.state.rows.map((row) => row.state.title).filter((title): title is string => !!title) + ); + for (const row of layout.state.rows) { if (row.state.repeatSourceKey) { continue; @@ -420,10 +424,14 @@ export class TabsLayoutManager extends SceneObjectBase i // We need to clear the target since we don't want to point the original row anymore (if it was set) conditionalRendering?.setTarget(undefined); + const newTitle = + row.state.title || generateUniqueTitle(t('dashboard.tabs-layout.tab.new', 'New tab'), existingNames); + existingNames.add(newTitle); + tabs.push( new TabItem({ layout: row.state.layout.clone(), - title: row.state.title, + title: newTitle, conditionalRendering, repeatByVariable: row.state.repeatByVariable, }) From a37ebf609e31ff3ac8852415dc2b659af564c7e4 Mon Sep 17 00:00:00 2001 From: Adela Almasan <88068998+adela-almasan@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:25:03 -0600 Subject: [PATCH 081/297] VizSuggestions: Fix unique key warning (#115112) --- .github/CODEOWNERS | 3 ++- .../VizTypePicker/VisualizationSuggestions.tsx | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d8a1e4104bf..47688554cce 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -520,7 +520,7 @@ i18next.config.ts @grafana/grafana-frontend-platform /e2e-playwright/various-suite/solo-route.spec.ts @grafana/dashboards-squad /e2e-playwright/various-suite/trace-view-scrolling.spec.ts @grafana/observability-traces-and-profiling /e2e-playwright/various-suite/verify-i18n.spec.ts @grafana/grafana-frontend-platform -/e2e-playwright/various-suite/visualization-suggestions.spec.ts @grafana/dashboards-squad +/e2e-playwright/various-suite/visualization-suggestions.spec.ts @grafana/dataviz-squad /e2e-playwright/various-suite/perf-test.spec.ts @grafana/grafana-frontend-platform # Packages @@ -956,6 +956,7 @@ playwright.storybook.config.ts @grafana/grafana-frontend-platform /public/app/features/notifications/ @grafana/grafana-search-navigate-organise /public/app/features/org/ @grafana/grafana-search-navigate-organise /public/app/features/panel/ @grafana/dashboards-squad +/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx @grafana/dataviz-squad /public/app/features/panel/suggestions/ @grafana/dataviz-squad /public/app/features/playlist/ @grafana/dashboards-squad /public/app/features/plugins/ @grafana/plugins-platform-frontend diff --git a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx index d7eed4ed2e7..e93e74f358d 100644 --- a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx +++ b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/css'; -import { useState, useEffect, useCallback, useMemo } from 'react'; +import { Fragment, useState, useEffect, useCallback, useMemo } from 'react'; import { useAsync, useMeasure } from 'react-use'; import { @@ -133,9 +133,9 @@ export function VisualizationSuggestions({ onChange, data, panel }: Props) { return (
{isNewVizSuggestionsEnabled - ? suggestionsByVizType.map(([vizType, vizTypeSuggestions]) => ( - <> -
+ ? suggestionsByVizType.map(([vizType, vizTypeSuggestions], groupIndex) => ( + +
{vizType?.info && } {vizType?.name || t('panel.visualization-suggestions.unknown-viz-type', 'Unknown visualization type')} @@ -190,7 +190,7 @@ export function VisualizationSuggestions({ onChange, data, panel }: Props) {
); })} - +
)) : suggestions?.map((suggestion, index) => (
From c73cab8eef57195679072285d2942529ea5e75ee Mon Sep 17 00:00:00 2001 From: Renato Costa <103441181+renatolabs@users.noreply.github.com> Date: Fri, 12 Dec 2025 13:56:47 -0500 Subject: [PATCH 082/297] chore: add cleanup task for duplicated provisioned dashboards (#115103) * chore: add cleanup task for duplicated provisioned dashboards --- pkg/services/dashboards/models.go | 11 +- .../dashboards/service/dashboard_service.go | 148 +++++++++- .../service/dashboard_service_test.go | 6 +- .../service/provisioning_cleanup.go | 107 +++++++ .../service/provisioning_cleanup_test.go | 279 ++++++++++++++++++ .../folderimpl/folder_unifiedstorage.go | 5 + pkg/services/folder/model.go | 13 +- .../provisioning/dashboards/dashboard.go | 13 +- 8 files changed, 561 insertions(+), 21 deletions(-) create mode 100644 pkg/services/dashboards/service/provisioning_cleanup.go create mode 100644 pkg/services/dashboards/service/provisioning_cleanup_test.go diff --git a/pkg/services/dashboards/models.go b/pkg/services/dashboards/models.go index 3c661ee6b9c..c68263db693 100644 --- a/pkg/services/dashboards/models.go +++ b/pkg/services/dashboards/models.go @@ -304,8 +304,15 @@ type DeleteDashboardCommand struct { RemovePermissions bool } +type ProvisioningConfig struct { + Name string + OrgID int64 + Folder string + AllowUIUpdates bool +} + type DeleteOrphanedProvisionedDashboardsCommand struct { - ReaderNames []string + Config []ProvisioningConfig } type DashboardProvisioningSearchResults struct { @@ -405,6 +412,8 @@ type DashboardSearchProjection struct { FolderTitle string SortMeta int64 Tags []string + ManagedBy utils.ManagerKind + ManagerId string Deleted *time.Time } diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 4c3bf3fea2f..03dd021a480 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -877,24 +877,32 @@ func (dr *DashboardServiceImpl) waitForSearchQuery(ctx context.Context, query *d } func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *dashboards.DeleteOrphanedProvisionedDashboardsCommand) error { - // cleanup duplicate provisioned dashboards first (this will have the same name and external_id) - // note: only works in modes 1-3 - if err := dr.DeleteDuplicateProvisionedDashboards(ctx); err != nil { - dr.log.Error("Failed to delete duplicate provisioned dashboards", "error", err) - } - // check each org for orphaned provisioned dashboards orgs, err := dr.orgService.Search(ctx, &org.SearchOrgsQuery{}) if err != nil { return err } + orgIDs := make([]int64, 0, len(orgs)) + for _, org := range orgs { + orgIDs = append(orgIDs, org.ID) + } + + if err := dr.DeleteDuplicateProvisionedDashboards(ctx, orgIDs, cmd.Config); err != nil { + dr.log.Error("Failed to delete duplicate provisioned dashboards", "error", err) + } + + currentNames := make([]string, 0, len(cmd.Config)) + for _, cfg := range cmd.Config { + currentNames = append(currentNames, cfg.Name) + } + for _, org := range orgs { ctx, _ := identity.WithServiceIdentity(ctx, org.ID) // find all dashboards in the org that have a file repo set that is not in the given readers list foundDashs, err := dr.searchProvisionedDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{ ManagedBy: utils.ManagerKindClassicFP, //nolint:staticcheck - ManagerIdentityNotIn: cmd.ReaderNames, + ManagerIdentityNotIn: currentNames, OrgId: org.ID, }) if err != nil { @@ -921,7 +929,129 @@ func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context. return nil } -func (dr *DashboardServiceImpl) DeleteDuplicateProvisionedDashboards(ctx context.Context) error { +// searchExistingProvisionedData fetches provisioned data for the purposes of +// duplication cleanup. Returns the set of folder UIDs for folders with the +// given title, and the set of resources contained in those folders. +func (dr *DashboardServiceImpl) searchExistingProvisionedData( + ctx context.Context, orgID int64, folderTitle string, +) ([]string, []dashboards.DashboardSearchProjection, error) { + ctx, user := identity.WithServiceIdentity(ctx, orgID) + cmd := folder.SearchFoldersQuery{ + OrgID: orgID, + SignedInUser: user, + Title: folderTitle, + TitleExactMatch: true, + } + + searchResults, err := dr.folderService.SearchFolders(ctx, cmd) + if err != nil { + return nil, nil, fmt.Errorf("checking if provisioning reset is required: %w", err) + } + + var matchingFolders []string //nolint:prealloc + for _, result := range searchResults { + f, err := dr.folderService.Get(ctx, &folder.GetFolderQuery{ + OrgID: orgID, + UID: &result.UID, + SignedInUser: user, + }) + if err != nil { + return nil, nil, err + } + + // We are only interested in folders at the top-level of the folder hierarchy. + // Cleanup is not performed for provisioned folders that were moved to + // a different location. + if f.ParentUID != "" { + continue + } + + matchingFolders = append(matchingFolders, f.UID) + } + + if len(matchingFolders) == 0 { + // If there are no folders with the same title as the provisioned folder we + // are looking for, there is nothing to be cleaned up. + return nil, nil, nil + } + + resources, err := dr.FindDashboards(ctx, &dashboards.FindPersistedDashboardsQuery{ + OrgId: orgID, + SignedInUser: user, + FolderUIDs: matchingFolders, + }) + if err != nil { + return nil, nil, err + } + + return matchingFolders, resources, nil +} + +// maybeResetProvisioning will check for duplicated provisioned dashboards in the database. These duplications +// happen when multiple provisioned dashboards of the same title are found, or multiple provisioned +// folders are found. In this case, provisioned resources are deleted, allowing the provisioning +// process to start from scratch after this function returns. +func (dr *DashboardServiceImpl) maybeResetProvisioning(ctx context.Context, orgs []int64, configs []dashboards.ProvisioningConfig) { + if skipReason := canBeAutomaticallyCleanedUp(configs); skipReason != "" { + dr.log.Info("not eligible for automated cleanup", "reason", skipReason) + return + } + + folderTitle := configs[0].Folder + provisionedNames := map[string]bool{} + for _, c := range configs { + provisionedNames[c.Name] = true + } + + for _, orgID := range orgs { + ctx, user := identity.WithServiceIdentity(ctx, orgID) + provFolders, resources, err := dr.searchExistingProvisionedData(ctx, orgID, folderTitle) + if err != nil { + dr.log.Error("failed to search for provisioned data for cleanup", "org", orgID, "error", err) + continue + } + + steps, err := cleanupSteps(provFolders, resources, provisionedNames) + if err != nil { + dr.log.Warn("not possible to perform automated duplicate cleanup", "org", orgID, "error", err) + continue + } + + for _, step := range steps { + var err error + + switch step.Type { + case searchstore.TypeDashboard: + err = dr.deleteDashboard(ctx, 0, step.UID, orgID, false) + case searchstore.TypeFolder: + err = dr.folderService.Delete(ctx, &folder.DeleteFolderCommand{ + OrgID: orgID, + SignedInUser: user, + UID: step.UID, + }) + } + + if err == nil { + dr.log.Info("deleted duplicated provisioned resource", + "type", step.Type, "uid", step.UID, + ) + } else { + dr.log.Error("failed to delete duplicated provisioned resource", + "type", step.Type, "uid", step.UID, "error", err, + ) + } + } + } +} + +func (dr *DashboardServiceImpl) DeleteDuplicateProvisionedDashboards(ctx context.Context, orgs []int64, configs []dashboards.ProvisioningConfig) error { + // Start from scratch if duplications that cannot be fixed by the logic + // below are found in the database. + dr.maybeResetProvisioning(ctx, orgs, configs) + + // cleanup duplicate provisioned dashboards (i.e., with the same name and external_id). + // Note: only works in modes 1-3. This logic can be removed once mode5 is + // enabled everywhere. duplicates, err := dr.dashboardStore.GetDuplicateProvisionedDashboards(ctx) if err != nil { return err @@ -1511,6 +1641,8 @@ func (dr *DashboardServiceImpl) FindDashboards(ctx context.Context, query *dashb FolderTitle: folderTitle, FolderID: folderID, FolderSlug: slugify.Slugify(folderTitle), + ManagedBy: hit.ManagedBy.Kind, + ManagerId: hit.ManagedBy.ID, Tags: hit.Tags, } diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index dafb925adb5..a3745f864c2 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -779,7 +779,7 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { }, nil).Twice() err := service.DeleteOrphanedProvisionedDashboards(context.Background(), &dashboards.DeleteOrphanedProvisionedDashboardsCommand{ - ReaderNames: []string{"test"}, + Config: []dashboards.ProvisioningConfig{{Name: "test"}}, }) require.NoError(t, err) k8sCliMock.AssertExpectations(t) @@ -874,7 +874,7 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { }, nil).Once() err := singleOrgService.DeleteOrphanedProvisionedDashboards(ctx, &dashboards.DeleteOrphanedProvisionedDashboardsCommand{ - ReaderNames: []string{"test"}, + Config: []dashboards.ProvisioningConfig{{Name: "test"}}, }) require.NoError(t, err) k8sCliMock.AssertExpectations(t) @@ -906,7 +906,7 @@ func TestDeleteOrphanedProvisionedDashboards(t *testing.T) { }, nil) err := singleOrgService.DeleteOrphanedProvisionedDashboards(ctx, &dashboards.DeleteOrphanedProvisionedDashboardsCommand{ - ReaderNames: []string{"test"}, + Config: []dashboards.ProvisioningConfig{{Name: "test"}}, }) require.NoError(t, err) k8sCliMock.AssertExpectations(t) diff --git a/pkg/services/dashboards/service/provisioning_cleanup.go b/pkg/services/dashboards/service/provisioning_cleanup.go new file mode 100644 index 00000000000..ca5fe75921a --- /dev/null +++ b/pkg/services/dashboards/service/provisioning_cleanup.go @@ -0,0 +1,107 @@ +package service + +import ( + "errors" + "fmt" + + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" +) + +// canBeAutomaticallyCleanedUp determines whether this instance can be automatically cleaned up +// if duplicated provisioned resources are found. To ensure the process does not delete +// resources it shouldn't, automatic cleanups only happen if all provisioned dashboards +// are stored in the same folder (by title), and no dashboards allow UI updates. +func canBeAutomaticallyCleanedUp(configs []dashboards.ProvisioningConfig) string { + if len(configs) == 0 { + return "no provisioned dashboards" + } + + folderTitle := configs[0].Folder + if len(folderTitle) == 0 { + return fmt.Sprintf("dashboard has no folder: %s", configs[0].Name) + } + + for _, cfg := range configs { + if cfg.AllowUIUpdates { + return "contains dashboards with allowUiUpdates" + } + + if cfg.Folder != folderTitle { + return "dashboards provisioned across multiple folders" + } + } + + return "" +} + +type deleteProvisionedResource struct { + Type string + UID string +} + +// cleanupSteps computes the sequence of steps to be performed in order to cleanup the +// provisioning resources and allow the process to start from scratch when duplication +// is detected. The sequence of steps will dictate the order in which dashboards and folders +// are to be deleted. +func cleanupSteps(provFolders []string, resources []dashboards.DashboardSearchProjection, configDashboards map[string]bool) ([]deleteProvisionedResource, error) { + var hasDuplicatedProvisionedDashboard bool + var hasUserCreatedResource bool + var uniqueNames = map[string]struct{}{} + var deleteProvisionedDashboards []deleteProvisionedResource //nolint:prealloc + + for _, r := range resources { + // nolint:staticcheck + if r.IsFolder || r.ManagedBy != utils.ManagerKindClassicFP { + hasUserCreatedResource = true + continue + } + + // Only delete dashboards if they are included in the provisioning configuration + // for this instance. + if !configDashboards[r.ManagerId] { + continue + } + + if _, exists := uniqueNames[r.ManagerId]; exists { + hasDuplicatedProvisionedDashboard = true + } + + uniqueNames[r.ManagerId] = struct{}{} + deleteProvisionedDashboards = append(deleteProvisionedDashboards, deleteProvisionedResource{ + Type: searchstore.TypeDashboard, + UID: r.UID, + }) + } + + if len(provFolders) == 0 { + // When there are no provisioned folders, there is nothing to do. + return nil, nil + } else if len(provFolders) == 1 { + // If only one folder was found, keep it and delete the provisioned dashboards if + // duplication was found. + if hasDuplicatedProvisionedDashboard { + return deleteProvisionedDashboards, nil + } + } else { + // If multiple folders were found *and* a user-created resource exists in + // one of them, bail, as we wouldn't be able to delete one of the duplicated folders. + if hasUserCreatedResource { + return nil, errors.New("multiple provisioning folders exist with at least one user-created resource") + } + + // Delete provisioned dashboards first, and then the folders. + steps := deleteProvisionedDashboards + for _, uid := range provFolders { + steps = append(steps, deleteProvisionedResource{ + Type: searchstore.TypeFolder, + UID: uid, + }) + } + + return steps, nil + } + + return nil, nil +} diff --git a/pkg/services/dashboards/service/provisioning_cleanup_test.go b/pkg/services/dashboards/service/provisioning_cleanup_test.go new file mode 100644 index 00000000000..049dc420f2d --- /dev/null +++ b/pkg/services/dashboards/service/provisioning_cleanup_test.go @@ -0,0 +1,279 @@ +package service + +import ( + "testing" + + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" + "github.com/stretchr/testify/require" +) + +func Test_canBeAutomaticallyCleanedUp(t *testing.T) { + testCases := []struct { + name string + configs []dashboards.ProvisioningConfig + expectedSkip string + }{ + { + name: "no dashboards defined in the configuration", + configs: []dashboards.ProvisioningConfig{}, + expectedSkip: "no provisioned dashboards", + }, + { + name: "first defined dashboard has no folder defined", + configs: []dashboards.ProvisioningConfig{ + {Name: "1", Folder: ""}, + {Folder: "f1"}, + }, + expectedSkip: "dashboard has no folder: 1", + }, + { + name: "one of the provisioned dashboards has no folder defined", + configs: []dashboards.ProvisioningConfig{ + {Name: "1", Folder: "f1"}, + {Name: "2", Folder: "f1"}, + {Name: "3", Folder: ""}, + {Name: "4", Folder: "f1"}, + }, + expectedSkip: "dashboards provisioned across multiple folders", + }, + { + name: "one of the provisioned dashboards allows UI updates", + configs: []dashboards.ProvisioningConfig{ + {Name: "1", Folder: "f1"}, + {Name: "2", Folder: "f1", AllowUIUpdates: true}, + {Name: "3", Folder: "f1"}, + {Name: "4", Folder: "f1"}, + }, + expectedSkip: "contains dashboards with allowUiUpdates", + }, + { + name: "one of the provisioned dashboards is in a different folder", + configs: []dashboards.ProvisioningConfig{ + {Name: "1", Folder: "f1"}, + {Name: "2", Folder: "f1"}, + {Name: "3", Folder: "f1"}, + {Name: "4", Folder: "different"}, + }, + expectedSkip: "dashboards provisioned across multiple folders", + }, + { + name: "can be skipped when all conditions are met", + configs: []dashboards.ProvisioningConfig{ + {Name: "1", Folder: "f1"}, + {Name: "2", Folder: "f1"}, + {Name: "3", Folder: "f1"}, + {Name: "4", Folder: "f1"}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.expectedSkip, canBeAutomaticallyCleanedUp(tc.configs)) + }) + } +} + +func Test_cleanupSteps(t *testing.T) { + isDashboard, isFolder := false, true + + fromUser := func(uid, name string, isFolder bool) dashboards.DashboardSearchProjection { + return dashboards.DashboardSearchProjection{ + UID: uid, + ManagerId: name, + IsFolder: isFolder, + } + } + + provisioned := func(uid, name string, isFolder bool) dashboards.DashboardSearchProjection { + dashboard := fromUser(uid, name, isFolder) + dashboard.ManagedBy = utils.ManagerKindClassicFP //nolint:staticcheck + return dashboard + } + + testCases := []struct { + name string + provisionedFolders []string + provisionedResources []dashboards.DashboardSearchProjection + configDashboards []string + expectedSteps []deleteProvisionedResource + expectedErr string + }{ + { + name: "no provisioned folders, nothing to do", + provisionedFolders: []string{}, + configDashboards: []string{"Provisioned1", "Provisioned2", "Provisioned3"}, + provisionedResources: []dashboards.DashboardSearchProjection{ + provisioned("d1", "Provisioned1", isDashboard), + }, + }, + { + name: "multiple folders, a user-created dashboard in one of them", + provisionedFolders: []string{"folder1", "folder2"}, + configDashboards: []string{"Provisioned1", "Provisioned2", "Provisioned3"}, + provisionedResources: []dashboards.DashboardSearchProjection{ + provisioned("d1", "Provisioned1", isDashboard), + provisioned("d2", "Provisioned2", isDashboard), + fromUser("d3", "User1", isDashboard), + provisioned("d4", "Provisioned3", isDashboard), + }, + expectedErr: "multiple provisioning folders exist with at least one user-created resource", + }, + { + name: "multiple folders, a user-created folder in one of them", + provisionedFolders: []string{"folder1", "folder2"}, + configDashboards: []string{"Provisioned1", "Provisioned2", "Provisioned3", "Provisioned4"}, + provisionedResources: []dashboards.DashboardSearchProjection{ + provisioned("d1", "Provisioned1", isDashboard), + provisioned("d2", "Provisioned2", isDashboard), + provisioned("d3", "Provisioned3", isDashboard), + fromUser("f1", "UserFolder1", isFolder), + }, + expectedErr: "multiple provisioning folders exist with at least one user-created resource", + }, + { + name: "single folder, some dashboards duplicated", + provisionedFolders: []string{"folder1"}, + configDashboards: []string{"Provisioned1", "Provisioned2", "Provisioned3", "Provisioned4"}, + provisionedResources: []dashboards.DashboardSearchProjection{ + // Provisioned1 is duplicated. + provisioned("d1", "Provisioned1", isDashboard), + provisioned("d2", "Provisioned2", isDashboard), + provisioned("d3", "Provisioned1", isDashboard), + provisioned("d4", "Provisioned3", isDashboard), + }, + expectedSteps: []deleteProvisionedResource{ + {Type: searchstore.TypeDashboard, UID: "d1"}, + {Type: searchstore.TypeDashboard, UID: "d2"}, + {Type: searchstore.TypeDashboard, UID: "d3"}, + {Type: searchstore.TypeDashboard, UID: "d4"}, + }, + }, + { + name: "single folder, duplicated dashboards, user-created dashboards are ignored", + provisionedFolders: []string{"folder1"}, + configDashboards: []string{"Provisioned1", "Provisioned2", "Provisioned3", "Provisioned4"}, + provisionedResources: []dashboards.DashboardSearchProjection{ + // Provisioned1 is duplicated. + provisioned("d1", "Provisioned1", isDashboard), + provisioned("d2", "Provisioned2", isDashboard), + fromUser("d3", "User1", isDashboard), + provisioned("d4", "Provisioned3", isDashboard), + provisioned("d5", "Provisioned1", isDashboard), + }, + // User dashboard (d3) is not deleted. + expectedSteps: []deleteProvisionedResource{ + {Type: searchstore.TypeDashboard, UID: "d1"}, + {Type: searchstore.TypeDashboard, UID: "d2"}, + {Type: searchstore.TypeDashboard, UID: "d4"}, + {Type: searchstore.TypeDashboard, UID: "d5"}, + }, + }, + { + name: "single folder, duplicated dashboards, user-created folders are ignored", + provisionedFolders: []string{"folder1"}, + configDashboards: []string{"Provisioned1", "Provisioned2", "Provisioned3"}, + provisionedResources: []dashboards.DashboardSearchProjection{ + // Provisioned1 is duplicated. + provisioned("d1", "Provisioned1", isDashboard), + provisioned("d2", "Provisioned2", isDashboard), + provisioned("d3", "Provisioned3", isDashboard), + provisioned("d4", "Provisioned1", isDashboard), + fromUser("f1", "UserFolder1", isFolder), + }, + // User folder (f1) is not deleted. + expectedSteps: []deleteProvisionedResource{ + {Type: searchstore.TypeDashboard, UID: "d1"}, + {Type: searchstore.TypeDashboard, UID: "d2"}, + {Type: searchstore.TypeDashboard, UID: "d3"}, + {Type: searchstore.TypeDashboard, UID: "d4"}, + }, + }, + { + name: "multiple folders, only provisioned dashboards", + provisionedFolders: []string{"folder1", "folder2"}, + configDashboards: []string{"Provisioned1", "Provisioned2", "Provisioned3", "Provisioned4"}, + provisionedResources: []dashboards.DashboardSearchProjection{ + provisioned("d1", "Provisioned1", isDashboard), + provisioned("d2", "Provisioned2", isDashboard), + provisioned("d3", "Provisioned3", isDashboard), + provisioned("d4", "Provisioned4", isDashboard), + }, + // Delete all dashboards, then all folders. + expectedSteps: []deleteProvisionedResource{ + {Type: searchstore.TypeDashboard, UID: "d1"}, + {Type: searchstore.TypeDashboard, UID: "d2"}, + {Type: searchstore.TypeDashboard, UID: "d3"}, + {Type: searchstore.TypeDashboard, UID: "d4"}, + {Type: searchstore.TypeFolder, UID: "folder1"}, + {Type: searchstore.TypeFolder, UID: "folder2"}, + }, + }, + { + name: "single folder, only deletes dashboards defined in the config file", + provisionedFolders: []string{"folder1"}, + configDashboards: []string{"Provisioned1", "Provisioned2"}, + provisionedResources: []dashboards.DashboardSearchProjection{ + provisioned("d1", "Provisioned1", isDashboard), + provisioned("d2", "Provisioned2", isDashboard), + provisioned("d3", "Provisioned1", isDashboard), + provisioned("d4", "Provisioned4", isDashboard), + provisioned("d5", "Provisioned4", isDashboard), + }, + // Delete duplicated dashboards, but keep Provisioned4, since it's not in the config file. + expectedSteps: []deleteProvisionedResource{ + {Type: searchstore.TypeDashboard, UID: "d1"}, + {Type: searchstore.TypeDashboard, UID: "d2"}, + {Type: searchstore.TypeDashboard, UID: "d3"}, + }, + }, + { + name: "single folder, no duplicated dashboards", + provisionedFolders: []string{"folder1"}, + configDashboards: []string{"Provisioned1", "Provisioned2", "Provisioned3", "Provisioned4"}, + provisionedResources: []dashboards.DashboardSearchProjection{ + provisioned("d1", "Provisioned1", isDashboard), + provisioned("d2", "Provisioned2", isDashboard), + provisioned("d3", "Provisioned3", isDashboard), + provisioned("d4", "Provisioned4", isDashboard), + }, + expectedSteps: nil, // no duplicates, nothing to do + }, + { + name: "single folder, no duplicated dashboards, multiple user-created resources", + provisionedFolders: []string{"folder1"}, + configDashboards: []string{"Provisioned1", "Provisioned2", "Provisioned3", "Provisioned4"}, + provisionedResources: []dashboards.DashboardSearchProjection{ + provisioned("d1", "Provisioned1", isDashboard), + provisioned("d2", "Provisioned2", isDashboard), + fromUser("f1", "UserFolder1", isFolder), + provisioned("d3", "Provisioned3", isDashboard), + fromUser("d4", "User1", isDashboard), + provisioned("d5", "Provisioned4", isDashboard), + fromUser("d6", "User2", isDashboard), + fromUser("f2", "UserFolder2", isFolder), + }, + expectedSteps: nil, // no duplicates in the provisioned set, nothing to do + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + provisionedSet := make(map[string]bool) + for _, name := range tc.configDashboards { + provisionedSet[name] = true + } + + steps, err := cleanupSteps(tc.provisionedFolders, tc.provisionedResources, provisionedSet) + if tc.expectedErr == "" { + require.NoError(t, err) + require.Equal(t, tc.expectedSteps, steps) + } else { + require.Error(t, err) + require.Equal(t, tc.expectedErr, err.Error()) + } + }) + } +} diff --git a/pkg/services/folder/folderimpl/folder_unifiedstorage.go b/pkg/services/folder/folderimpl/folder_unifiedstorage.go index 1551d858efe..9b238b769fe 100644 --- a/pkg/services/folder/folderimpl/folder_unifiedstorage.go +++ b/pkg/services/folder/folderimpl/folder_unifiedstorage.go @@ -202,6 +202,11 @@ func (s *Service) searchFoldersFromApiServer(ctx context.Context, query folder.S if query.Title != "" { // allow wildcard search request.Query = "*" + strings.ToLower(query.Title) + "*" + // or perform exact match if requested + if query.TitleExactMatch { + request.Query = query.Title + } + // if using query, you need to specify the fields you want request.Fields = dashboardsearch.IncludeFields } diff --git a/pkg/services/folder/model.go b/pkg/services/folder/model.go index 3e59f5c1b6f..e0061ca8dd7 100644 --- a/pkg/services/folder/model.go +++ b/pkg/services/folder/model.go @@ -224,12 +224,13 @@ type GetFoldersQuery struct { } type SearchFoldersQuery struct { - OrgID int64 - UIDs []string - IDs []int64 - Title string - Limit int64 - SignedInUser identity.Requester `json:"-"` + OrgID int64 + UIDs []string + IDs []int64 + Title string + TitleExactMatch bool + Limit int64 + SignedInUser identity.Requester `json:"-"` } // GetParentsQuery captures the information required by the folder service to diff --git a/pkg/services/provisioning/dashboards/dashboard.go b/pkg/services/provisioning/dashboards/dashboard.go index 72b980e4198..36cacdaf12a 100644 --- a/pkg/services/provisioning/dashboards/dashboard.go +++ b/pkg/services/provisioning/dashboards/dashboard.go @@ -153,13 +153,20 @@ func (provider *Provisioner) Provision(ctx context.Context) error { // CleanUpOrphanedDashboards deletes provisioned dashboards missing a linked reader. func (provider *Provisioner) CleanUpOrphanedDashboards(ctx context.Context) { - currentReaders := make([]string, len(provider.fileReaders)) + configs := make([]dashboards.ProvisioningConfig, len(provider.fileReaders)) for index, reader := range provider.fileReaders { - currentReaders[index] = reader.Cfg.Name + configs[index] = dashboards.ProvisioningConfig{ + Name: reader.Cfg.Name, + OrgID: reader.Cfg.OrgID, + Folder: reader.Cfg.Folder, + AllowUIUpdates: reader.Cfg.AllowUIUpdates, + } } - if err := provider.provisioner.DeleteOrphanedProvisionedDashboards(ctx, &dashboards.DeleteOrphanedProvisionedDashboardsCommand{ReaderNames: currentReaders}); err != nil { + if err := provider.provisioner.DeleteOrphanedProvisionedDashboards( + ctx, &dashboards.DeleteOrphanedProvisionedDashboardsCommand{Config: configs}, + ); err != nil { provider.log.Warn("Failed to delete orphaned provisioned dashboards", "err", err) } } From 4817ecf6a30cc86b49deadeec5482a054a5045ae Mon Sep 17 00:00:00 2001 From: Paul Marbach Date: Fri, 12 Dec 2025 13:59:54 -0500 Subject: [PATCH 083/297] Sparkline: Guess decimals rather than going with 0 (#115246) * Sparkline: Guess decimals rather than going with 0 * Update packages/grafana-ui/src/components/Sparkline/utils.test.ts --- .../src/components/Sparkline/utils.test.ts | 14 +++++++++++++- .../grafana-ui/src/components/Sparkline/utils.ts | 11 +++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/grafana-ui/src/components/Sparkline/utils.test.ts b/packages/grafana-ui/src/components/Sparkline/utils.test.ts index 2a77677d998..ca49f6da512 100644 --- a/packages/grafana-ui/src/components/Sparkline/utils.test.ts +++ b/packages/grafana-ui/src/components/Sparkline/utils.test.ts @@ -119,7 +119,14 @@ describe('Get y range', () => { values: [2, 1.999999999999999, 2.000000000000001, 2, 2], type: FieldType.number, config: {}, - state: { range: { min: 1.999999999999999, max: 2.000000000000001, delta: 0 } }, + state: { range: { min: 1.9999999999999999999, max: 2.000000000000000001, delta: 0 } }, + }; + const decimalsNotCloseYField: Field = { + name: 'y', + values: [2, 0.0094, 0.0053, 0.0078, 0.0061], + type: FieldType.number, + config: {}, + state: { range: { min: 0.0053, max: 0.0094, delta: 0.0041 } }, }; const xField: Field = { name: 'x', @@ -183,6 +190,11 @@ describe('Get y range', () => { field: decimalsCloseYField, expected: [2, 4], }, + { + description: 'decimal values which are not close to equal should not be rounded out', + field: decimalsNotCloseYField, + expected: [0.0053, 0.0094], + }, ])(`should return correct range for $description`, ({ field, expected }) => { const actual = getYRange(getAlignedFrame(field)); expect(actual).toEqual(expected); diff --git a/packages/grafana-ui/src/components/Sparkline/utils.ts b/packages/grafana-ui/src/components/Sparkline/utils.ts index e4d17a85c15..be24eb6c4e8 100644 --- a/packages/grafana-ui/src/components/Sparkline/utils.ts +++ b/packages/grafana-ui/src/components/Sparkline/utils.ts @@ -8,6 +8,7 @@ import { FieldType, getFieldColorModeForField, GrafanaTheme2, + guessDecimals, isLikelyAscendingVector, nullToValue, roundDecimals, @@ -76,8 +77,6 @@ export function getYRange(alignedFrame: DataFrame): Range.MinMax { min = Math.min(min!, field.config.min ?? Infinity); max = Math.max(max!, field.config.max ?? -Infinity); - // console.log({ min, max }); - // if noValue is set, ensure that it is included in the range as well const noValue = +field.config?.noValue!; if (!Number.isNaN(noValue)) { @@ -85,9 +84,11 @@ export function getYRange(alignedFrame: DataFrame): Range.MinMax { max = Math.max(max, noValue); } + const decimals = field.config.decimals ?? Math.max(guessDecimals(min), guessDecimals(max)); + // call roundDecimals to mirror what is going to eventually happen in uplot - let roundedMin = roundDecimals(min, field.config.decimals ?? 0); - let roundedMax = roundDecimals(max, field.config.decimals ?? 0); + let roundedMin = roundDecimals(min, decimals); + let roundedMax = roundDecimals(max, decimals); // if the rounded min and max are different, // we can return the real min and max. @@ -102,11 +103,9 @@ export function getYRange(alignedFrame: DataFrame): Range.MinMax { roundedMax = 1; } else if (roundedMin < 0) { // both are negative - // max = 0; roundedMin *= 2; } else { // both are positive - // min = 0; roundedMax *= 2; } From 5156177079f9f539f7ff453197e14c5734cda33e Mon Sep 17 00:00:00 2001 From: colin-stuart Date: Fri, 12 Dec 2025 13:51:37 -0600 Subject: [PATCH 084/297] SCIM: show error if SCIM-provisioned user attempts login with non-SAML auth module (#115271) --- .../authn/authnimpl/sync/user_sync.go | 19 ++++ .../authn/authnimpl/sync/user_sync_test.go | 97 +++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/pkg/services/authn/authnimpl/sync/user_sync.go b/pkg/services/authn/authnimpl/sync/user_sync.go index cb8099ba1d1..ada03081add 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync.go +++ b/pkg/services/authn/authnimpl/sync/user_sync.go @@ -77,6 +77,10 @@ var ( "user.sync.user-externalUID-mismatch", errutil.WithPublicMessage("User externalUID mismatch"), ) + errSCIMAuthModuleMismatch = errutil.Unauthorized( + "user.sync.scim-auth-module-mismatch", + errutil.WithPublicMessage("User was provisioned via SCIM and must login via SAML"), + ) ) var ( @@ -308,6 +312,21 @@ func (s *UserSync) SyncUserHook(ctx context.Context, id *authn.Identity, _ *auth // just try to fetch the user one more to make the other request work. if errors.Is(err, user.ErrUserAlreadyExists) { usr, _, err = s.getUser(ctx, id) + + // Check if this is a SCIM-provisioned user trying to login via an auth module that is not SAML or GCOM + if err == nil && usr != nil && usr.IsProvisioned && id.AuthenticatedBy != login.GrafanaComAuthModule { + _, authErr := s.authInfoService.GetAuthInfo(ctx, &login.GetAuthInfoQuery{ + UserId: usr.ID, + AuthModule: id.AuthenticatedBy, + }) + if errors.Is(authErr, user.ErrUserNotFound) { + s.log.FromContext(ctx).Error("SCIM-provisioned user attempted login via non-SAML auth module", + "user_id", usr.ID, + "attempted_module", id.AuthenticatedBy, + ) + return errSCIMAuthModuleMismatch.Errorf("user was provisioned via SCIM but attempted login via %s", id.AuthenticatedBy) + } + } } if err != nil { diff --git a/pkg/services/authn/authnimpl/sync/user_sync_test.go b/pkg/services/authn/authnimpl/sync/user_sync_test.go index dd19836b0a5..ad863164aee 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/user_sync_test.go @@ -1926,3 +1926,100 @@ func TestUserSync_SCIMLoginUsageStatSet(t *testing.T) { finalCount := finalStats["stats.features.scim.has_successful_login.count"].(int) require.Equal(t, int(1), finalCount) } + +func TestUserSync_SyncUserHook_SCIMAuthModuleMismatch(t *testing.T) { + userSrv := usertest.NewMockService(t) + authInfoSrv := authinfotest.NewMockAuthInfoService(t) + + userSrv.On("GetByEmail", mock.Anything, mock.Anything).Return(nil, user.ErrUserNotFound).Once() + + userSrv.On("Create", mock.Anything, mock.Anything).Return(nil, user.ErrUserAlreadyExists).Once() + + userSrv.On("GetByEmail", mock.Anything, mock.Anything).Return(&user.User{ + ID: 1, + Email: "test@test.com", + IsProvisioned: true, + }, nil).Once() + + authInfoSrv.On("GetAuthInfo", mock.Anything, mock.MatchedBy(func(q *login.GetAuthInfoQuery) bool { + return q.AuthModule == "oauth_azuread" + })).Return(nil, user.ErrUserNotFound).Once() + + s := ProvideUserSync( + userSrv, + authinfoimpl.ProvideOSSUserProtectionService(), + authInfoSrv, + "atest.FakeQuotaService{}, + tracing.NewNoopTracerService(), + featuremgmt.WithFeatures(), + setting.NewCfg(), + nil, + ) + + email := "test@test.com" + + err := s.SyncUserHook(context.Background(), &authn.Identity{ + AuthenticatedBy: "oauth_azuread", + ClientParams: authn.ClientParams{ + SyncUser: true, + AllowSignUp: true, + LookUpParams: login.UserLookupParams{ + Email: &email, + }, + }, + }, nil) + + require.Error(t, err) + assert.ErrorIs(t, err, errSCIMAuthModuleMismatch) + assert.Contains(t, err.Error(), "SCIM") + assert.Contains(t, err.Error(), "oauth_azuread") +} + +func TestUserSync_SyncUserHook_SCIMUserAllowsGCOMLogin(t *testing.T) { + userSrv := usertest.NewMockService(t) + authInfoSrv := authinfotest.NewMockAuthInfoService(t) + + authInfoSrv.On("GetAuthInfo", mock.Anything, mock.MatchedBy(func(q *login.GetAuthInfoQuery) bool { + return q.AuthModule == login.GrafanaComAuthModule && q.AuthId == "gcom-user-123" + })).Return(nil, user.ErrUserNotFound).Once() + + userSrv.On("GetByEmail", mock.Anything, mock.Anything).Return(nil, user.ErrUserNotFound).Once() + userSrv.On("Create", mock.Anything, mock.Anything).Return(nil, user.ErrUserAlreadyExists).Once() + + authInfoSrv.On("GetAuthInfo", mock.Anything, mock.MatchedBy(func(q *login.GetAuthInfoQuery) bool { + return q.AuthModule == login.GrafanaComAuthModule && q.AuthId == "gcom-user-123" + })).Return(nil, user.ErrUserNotFound).Once() + + userSrv.On("GetByEmail", mock.Anything, mock.Anything).Return(&user.User{ + ID: 1, + Email: "test@test.com", + IsProvisioned: true, + }, nil).Once() + + s := ProvideUserSync( + userSrv, + authinfoimpl.ProvideOSSUserProtectionService(), + authInfoSrv, + "atest.FakeQuotaService{}, + tracing.NewNoopTracerService(), + featuremgmt.WithFeatures(), + setting.NewCfg(), + nil, + ) + + email := "test@test.com" + + err := s.SyncUserHook(context.Background(), &authn.Identity{ + AuthenticatedBy: login.GrafanaComAuthModule, + AuthID: "gcom-user-123", + ClientParams: authn.ClientParams{ + SyncUser: true, + AllowSignUp: true, + LookUpParams: login.UserLookupParams{ + Email: &email, + }, + }, + }, nil) + + require.NoError(t, err) +} From 37ccd8bc3dbd245098d7f62b72ff8d0ad6ebce09 Mon Sep 17 00:00:00 2001 From: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:21:50 -0600 Subject: [PATCH 085/297] Docs: Added troubleshooting guide for the InfluxDB data source (#115191) * Docs: Add troubleshooting guide for InfluxDB data source * linter fixes, updates based on feedback --- docs/sources/datasources/influxdb/_index.md | 1 + .../influxdb/troubleshooting/index.md | 291 ++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 docs/sources/datasources/influxdb/troubleshooting/index.md diff --git a/docs/sources/datasources/influxdb/_index.md b/docs/sources/datasources/influxdb/_index.md index c3f722d848f..4ede6c02c52 100644 --- a/docs/sources/datasources/influxdb/_index.md +++ b/docs/sources/datasources/influxdb/_index.md @@ -52,6 +52,7 @@ The following documents will help you get started with the InfluxDB data source - [Configure the InfluxDB data source](./configure-influxdb-data-source/) - [InfluxDB query editor](./query-editor/) - [InfluxDB templates and variables](./template-variables/) +- [Troubleshoot issues with the InfluxDB data source](./troubleshooting/) Once you have configured the data source you can: diff --git a/docs/sources/datasources/influxdb/troubleshooting/index.md b/docs/sources/datasources/influxdb/troubleshooting/index.md new file mode 100644 index 00000000000..33fe67ecadf --- /dev/null +++ b/docs/sources/datasources/influxdb/troubleshooting/index.md @@ -0,0 +1,291 @@ +--- +aliases: + - ../../data-sources/influxdb/troubleshooting/ +description: Troubleshooting the InfluxDB data source in Grafana +keywords: + - grafana + - influxdb + - troubleshooting + - errors + - flux + - influxql + - sql +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Troubleshooting +title: Troubleshoot issues with the InfluxDB data source +weight: 600 +--- + +# Troubleshoot issues with the InfluxDB data source + +This document provides troubleshooting information for common errors you may encounter when using the InfluxDB data source in Grafana. + +## Connection errors + +The following errors occur when Grafana cannot establish or maintain a connection to InfluxDB. + +### Failed to connect to InfluxDB + +**Error message:** "error performing influxQL query" or "error performing flux query" or "error performing sql query" + +**Cause:** Grafana cannot establish a network connection to the InfluxDB server. + +**Solution:** + +1. Verify that the InfluxDB URL is correct in the data source configuration. +1. Check that InfluxDB is running and accessible from the Grafana server. +1. Ensure the URL includes the protocol (`http://` or `https://`). +1. Verify the port is correct (the InfluxDB default API port is `8086`). +1. Ensure there are no firewall rules blocking the connection. +1. For Grafana Cloud, ensure you have configured [Private data source connect](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/) if your InfluxDB instance is not publicly accessible. + +### Request timed out + +**Error message:** "context deadline exceeded" or "request timeout" + +**Cause:** The connection to InfluxDB timed out before receiving a response. + +**Solution:** + +1. Check the network latency between Grafana and InfluxDB. +1. Verify that InfluxDB is not overloaded or experiencing performance issues. +1. Increase the timeout setting in the data source configuration under **Advanced HTTP Settings**. +1. Reduce the time range or complexity of your query. + +## Authentication errors + +The following errors occur when there are issues with authentication credentials or permissions. + +### Unauthorized (401) + +**Error message:** "401 Unauthorized" or "authorization failed" + +**Cause:** The authentication credentials are invalid or missing. + +**Solution:** + +1. Verify that the token or password is correct in the data source configuration. +1. For Flux and SQL, ensure the token has not expired. +1. For InfluxQL with InfluxDB 2.x, verify the token is set as an `Authorization` header with the value `Token `. +1. For InfluxDB 1.x, verify the username and password are correct. +1. Check that the token has the required permissions to access the specified bucket or database. + +### Forbidden (403) + +**Error message:** "403 Forbidden" or "access denied" + +**Cause:** The authenticated user or token does not have permission to access the requested resource. + +**Solution:** + +1. Verify the token has read access to the specified bucket or database. +1. Check the token's permissions in the InfluxDB UI under **API Tokens**. +1. Ensure the organization ID is correct for Flux queries. +1. For InfluxQL with InfluxDB 2.x, verify the DBRP mapping is configured correctly. + +## Configuration errors + +The following errors occur when the data source is not configured correctly. + +### Unknown influx version + +**Error message:** "unknown influx version" + +**Cause:** The query language is not properly configured in the data source settings. + +**Solution:** + +1. Open the data source configuration in Grafana. +1. Verify that a valid query language is selected: **Flux**, **InfluxQL**, or **SQL**. +1. Ensure the selected query language matches your InfluxDB version: + - Flux: InfluxDB 1.8+ and 2.x + - InfluxQL: InfluxDB 1.x and 2.x (with DBRP mapping) + - SQL: InfluxDB 3.x only + +### Invalid data source info received + +**Error message:** "invalid data source info received" + +**Cause:** The data source configuration is incomplete or corrupted. + +**Solution:** + +1. Delete and recreate the data source. +1. Ensure all required fields are populated based on your query language: + - **Flux:** URL, Organization, Token, Default Bucket + - **InfluxQL:** URL, Database, User, Password + - **SQL:** URL, Database, Token + +### DBRP mapping required + +**Error message:** "database not found" or queries return no data with InfluxQL on InfluxDB 2.x + +**Cause:** InfluxQL queries on InfluxDB 2.x require a Database and Retention Policy (DBRP) mapping. + +**Solution:** + +1. Create a DBRP mapping in InfluxDB using the CLI or API. +1. Refer to [Manage DBRP Mappings](https://docs.influxdata.com/influxdb/cloud/query-data/influxql/dbrp/) for guidance. +1. Verify the database name in Grafana matches the DBRP mapping. + +## Query errors + +The following errors occur when there are issues with query syntax or execution. + +### Query syntax error + +**Error message:** "error parsing query: found THING" or "failed to parse query: found WERE, expected ; at line 1, char 38" + +**Cause:** The query contains invalid syntax. + +**Solution:** + +1. Check your query syntax for typos or invalid keywords. +1. For InfluxQL, verify the query follows the correct syntax: + + ```sql + SELECT FROM WHERE + ``` + +1. For Flux, ensure proper pipe-forward syntax and function calls. +1. Use the InfluxDB UI or CLI to test your query directly. + +### Query timeout limit exceeded + +**Error message:** "query-timeout limit exceeded" + +**Cause:** The query took longer than the configured timeout limit in InfluxDB. + +**Solution:** + +1. Reduce the time range of your query. +1. Add more specific filters to limit the data scanned. +1. Increase the query timeout setting in InfluxDB if you have admin access. +1. Optimize your query to reduce complexity. + +### Too many series or data points + +**Error message:** "max-series-per-database limit exceeded" or "A query returned too many data points and the results have been truncated" + +**Cause:** The query is returning more data than the configured limits allow. + +**Solution:** + +1. Reduce the time range of your query. +1. Add filters to limit the number of series returned. +1. Increase the **Max series** setting in the data source configuration under **Advanced Database Settings**. +1. Use aggregation functions to reduce the number of data points. +1. For Flux, use `aggregateWindow()` to downsample data. + +### No time column found + +**Error message:** "no time column found" + +**Cause:** The query result does not include a time column, which is required for time series visualization. + +**Solution:** + +1. Ensure your query includes a time field. +1. For Flux, verify the query includes `_time` in the output. +1. For SQL, ensure the query returns a timestamp column. +1. Check that the time field is not being filtered out or excluded. + +## Health check errors + +The following errors occur when testing the data source connection. + +### Error getting flux query buckets + +**Error message:** "error getting flux query buckets" + +**Cause:** The health check query `buckets()` failed to return results. + +**Solution:** + +1. Verify the token has permission to list buckets. +1. Check that the organization ID is correct. +1. Ensure InfluxDB is running and accessible. + +### Error connecting InfluxDB influxQL + +**Error message:** "error connecting InfluxDB influxQL" + +**Cause:** The health check query `SHOW MEASUREMENTS` failed. + +**Solution:** + +1. Verify the database name is correct. +1. Check that the user has permission to run `SHOW MEASUREMENTS`. +1. Ensure the database exists and contains measurements. +1. For InfluxDB 2.x, verify DBRP mapping is configured. + +### 0 measurements found + +**Error message:** "data source is working. 0 measurements found" + +**Cause:** The connection is successful, but the database contains no measurements. + +**Solution:** + +1. Verify you are connecting to the correct database. +1. Check that data has been written to the database. +1. If the database is new, add some test data to verify the connection. + +## Other common issues + +The following issues don't produce specific error messages but are commonly encountered. + +### Empty query results + +**Cause:** The query returns no data. + +**Solution:** + +1. Verify the time range includes data in your database. +1. Check that the measurement and field names are correct. +1. Test the query directly in the InfluxDB UI or CLI. +1. Ensure filters are not excluding all data. +1. For InfluxQL, verify the retention policy contains data for the selected time range. + +### Slow query performance + +**Cause:** Queries take a long time to execute. + +**Solution:** + +1. Reduce the time range of your query. +1. Add more specific filters to limit the data scanned. +1. Increase the **Min time interval** setting to reduce the number of data points. +1. Check InfluxDB server performance and resource utilization. +1. For Flux, use `aggregateWindow()` to downsample data before visualization. +1. Consider using continuous queries or tasks to pre-aggregate data. + +### Data appears delayed or missing recent points + +**Cause:** The visualization doesn't show the most recent data. + +**Solution:** + +1. Check the dashboard time range and refresh settings. +1. Verify the **Min time interval** is not set too high. +1. Ensure InfluxDB has finished writing the data. +1. Check for clock synchronization issues between Grafana and InfluxDB. + +## Get additional help + +If you continue to experience issues after following this troubleshooting guide: + +1. Check the [InfluxDB documentation](https://docs.influxdata.com/) for API-specific guidance. +1. Review the [Grafana community forums](https://community.grafana.com/) for similar issues. +1. Contact Grafana Support if you're an Enterprise, Cloud Pro or Cloud Contracted user. +1. When reporting issues, include: + - Grafana version + - InfluxDB version and product (OSS, Cloud, Enterprise) + - Query language (Flux, InfluxQL, or SQL) + - Error messages (redact sensitive information) + - Steps to reproduce + - Relevant configuration such as data source settings, HTTP method, and TLS settings (redact tokens, passwords, and other credentials) From 3459c67bfbfc69a6b2f621b2f43147f565803d8e Mon Sep 17 00:00:00 2001 From: Larissa Wandzura <126723338+lwandz13@users.noreply.github.com> Date: Fri, 12 Dec 2025 15:10:03 -0600 Subject: [PATCH 086/297] DOCS: Overhaul Azure Monitor data source docs (#115121) * continued edits * authentication updates * added more info to configure doc * started work on query editor * reviewed the configure doc, consolidated sections * fixed issue with headings * fixed errors * updates to the template variables doc * created initial troubleshooting doc * removed gerunds and fixed heading issues * new annotations doc added * more updates to query editor * fixed spelling * fixed some linter issues * fixed flow for the intro doc * updates to the intro doc * fixed transformation links * added review date to front matter * ran prettier * added a new alerting doc * linter updates * some final edits * ran prettier again * Update docs/sources/datasources/azure-monitor/configure/index.md Co-authored-by: Andreas Christou * Update docs/sources/datasources/azure-monitor/configure/index.md Co-authored-by: Andreas Christou * Update docs/sources/datasources/azure-monitor/troubleshooting/index.md Co-authored-by: Andreas Christou * edits based on feedback * removed all relative reference links * ran prettier --------- Co-authored-by: Andreas Christou --- .../datasources/azure-monitor/_index.md | 348 +++------- .../azure-monitor/alerting/index.md | 262 ++++++++ .../azure-monitor/annotations/index.md | 218 +++++++ .../azure-monitor/configure/index.md | 605 ++++++++++++++++++ .../azure-monitor/query-editor/index.md | 160 ++++- .../azure-monitor/template-variables/index.md | 187 +++++- .../azure-monitor/troubleshooting/index.md | 320 +++++++++ 7 files changed, 1766 insertions(+), 334 deletions(-) create mode 100644 docs/sources/datasources/azure-monitor/alerting/index.md create mode 100644 docs/sources/datasources/azure-monitor/annotations/index.md create mode 100644 docs/sources/datasources/azure-monitor/configure/index.md create mode 100644 docs/sources/datasources/azure-monitor/troubleshooting/index.md diff --git a/docs/sources/datasources/azure-monitor/_index.md b/docs/sources/datasources/azure-monitor/_index.md index 90452f4cc52..d66b19efdff 100644 --- a/docs/sources/datasources/azure-monitor/_index.md +++ b/docs/sources/datasources/azure-monitor/_index.md @@ -3,7 +3,6 @@ aliases: - ../data-sources/azure-monitor/ - ../features/datasources/azuremonitor/ - azuremonitor/ - - azuremonitor/deprecated-application-insights/ description: Guide for using Azure Monitor in Grafana keywords: - grafana @@ -23,6 +22,7 @@ labels: menuTitle: Azure Monitor title: Azure Monitor data source weight: 300 +last_reviewed: 2025-12-04 refs: configure-grafana-feature-toggles: - pattern: /docs/grafana/ @@ -49,6 +49,11 @@ refs: destination: /docs/grafana//dashboards/build-dashboards/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana//dashboards/build-dashboards/ + transform-data: + - pattern: /docs/grafana/ + destination: /docs/grafana//panels-visualizations/query-transform-data/transform-data/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//panels-visualizations/query-transform-data/transform-data/ configure-grafana-azure: - pattern: /docs/grafana/ destination: /docs/grafana//setup-grafana/configure-grafana/#azure @@ -63,295 +68,98 @@ refs: - pattern: /docs/grafana/ destination: /docs/grafana//setup-grafana/configure-access/configure-authentication/azuread/#enable-azure-ad-oauth-in-grafana - pattern: /docs/grafana-cloud/ - destination: /docs/grafana//setup-grafana/configure-access/configure-authentication/azuread/#enable-azure-ad-oauth-in-grafana + destination: /docs/grafana//setup-grafana/configure-security/configure-authentication/azuread/#enable-azure-ad-oauth-in-grafana + configure-azure-monitor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/configure/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/configure/ + query-editor-azure-monitor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/query-editor/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/query-editor/ + template-variables-azure-monitor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/template-variables/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/template-variables/ + alerting-azure-monitor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/alerting/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/alerting/ + troubleshooting-azure-monitor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/troubleshooting/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/troubleshooting/ + annotations-azure-monitor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/annotations/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/annotations/ --- # Azure Monitor data source -Grafana ships with built-in support for Azure Monitor, the Azure service to maximize the availability and performance of applications and services in the Azure Cloud. -This topic explains configuring and querying specific to the Azure Monitor data source. +The Azure Monitor data source plugin allows you to query and visualize data from Azure Monitor, the Azure service to maximize the availability and performance of applications and services in the Azure Cloud. -For instructions on how to add a data source to Grafana, refer to the [administration documentation](ref:data-source-management). -Only users with the organization administrator role can add data sources. +## Supported Azure clouds -Once you've added the Azure Monitor data source, you can [configure it](#configure-the-data-source) so that your Grafana instance's users can create queries in its [query editor](query-editor/) when they [build dashboards](ref:build-dashboards) and use [Explore](ref:explore). +The Azure Monitor data source supports the following Azure cloud environments: -The Azure Monitor data source supports visualizing data from four Azure services: +- **Azure** - Azure public cloud (default) +- **Azure US Government** - Azure Government cloud +- **Azure China** - Azure China cloud operated by 21Vianet -- **Azure Monitor Metrics:** Collect numeric data from resources in your Azure account. -- **Azure Monitor Logs:** Collect log and performance data from your Azure account, and query using the Kusto Query Language (KQL). -- **Azure Resource Graph:** Query your Azure resources across subscriptions. -- **Azure Monitor Application Insights:** Collect trace logging data and other application performance metrics. +## Supported Azure services -## Configure the data source +The Azure Monitor data source supports the following Azure services: -**To access the data source configuration page:** +| Service | Description | +| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| **Azure Monitor Metrics** | Collect numeric data from resources in your Azure account. Supports dimensions, aggregations, and time grain configuration. | +| **Azure Monitor Logs** | Collect log and performance data from your Azure account using the Kusto Query Language (KQL). | +| **Azure Resource Graph** | Query your Azure resources across subscriptions using KQL. Useful for inventory, compliance, and resource management. | +| **Application Insights Traces** | Collect distributed trace data and correlate requests across your application components. | -1. Click **Connections** in the left-side menu. -1. Under Your connections, click **Data sources**. -1. Enter `Azure Monitor` in the search bar. -1. Click **Azure Monitor**. +## Get started - The **Settings** tab of the data source is displayed. +The following documents will help you get started with the Azure Monitor data source: -### Configure Azure Active Directory (AD) authentication +- [Configure the Azure Monitor data source](ref:configure-azure-monitor) - Set up authentication and connect to Azure +- [Azure Monitor query editor](ref:query-editor-azure-monitor) - Create and edit queries for Metrics, Logs, Traces, and Resource Graph +- [Template variables](ref:template-variables-azure-monitor) - Create dynamic dashboards with Azure Monitor variables +- [Alerting](ref:alerting-azure-monitor) - Create alert rules using Azure Monitor data +- [Troubleshooting](ref:troubleshooting-azure-monitor) - Solve common configuration and query errors -You must create an app registration and service principal in Azure AD to authenticate the data source. -For configuration details, refer to the [Azure documentation for service principals](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#get-tenant-and-app-id-values-for-signing-in). +## Additional features -The app registration you create must have the `Reader` role assigned on the subscription. -For more information, refer to [Azure documentation for role assignments](https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal?tabs=current). +After you have configured the Azure Monitor data source, you can: -If you host Grafana in Azure, such as in App Service or Azure Virtual Machines, you can configure the Azure Monitor data source to use Managed Identity for secure authentication without entering credentials into Grafana. -For details, refer to [Configuring using Managed Identity](#configuring-using-managed-identity). +- Add [Annotations](ref:annotations-azure-monitor) to overlay Azure log events on your graphs. +- Configure and use [Template variables](ref:template-variables-azure-monitor) for dynamic dashboards. +- Add [Transformations](ref:transform-data) to manipulate query results. +- Set up [Alerting](ref:alerting-azure-monitor) and recording rules using Metrics, Logs, Traces, and Resource Graph queries. +- Use [Explore](ref:explore) to investigate your Azure data without building a dashboard. -You can configure the Azure Monitor data source to use Workload Identity for secure authentication without entering credentials into Grafana if you host Grafana in a Kubernetes environment, such as AKS, and require access to Azure resources. -For details, refer to [Configuring using Workload Identity](#configuring-using-workload-identity). +## Pre-built dashboards -| Name | Description | -| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Authentication** | Enables Managed Identity. Selecting Managed Identity hides many of the other fields. For details, see [Configuring using Managed Identity](#configuring-using-managed-identity). | -| **Azure Cloud** | Sets the national cloud for your Azure account. For most users, this is the default "Azure". For details, see the [Azure documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-national-cloud). | -| **Directory (tenant) ID** | Sets the directory/tenant ID for the Azure AD app registration to use for authentication. For details, see the [Azure tenant and app ID docs](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#get-tenant-and-app-id-values-for-signing-in). | -| **Application (client) ID** | Sets the application/client ID for the Azure AD app registration to use for authentication. | -| **Client secret** | Sets the application client secret for the Azure AD app registration to use for authentication. For details, see the [Azure application secret docs](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#option-2-create-a-new-application-secret). | -| **Default subscription** | _(Optional)_ Sets a default subscription for template variables to use. | -| **Enable Basic Logs** | Allows this data source to execute queries against [Basic Logs tables](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/basic-logs-query?tabs=portal-1) in supported Log Analytics Workspaces. These queries may incur additional costs. | +The Azure Monitor plugin includes the following pre-built dashboards: -### Provision the data source +- **Azure Monitor Overview** - Displays key metrics across your Azure subscriptions and resources. +- **Azure Storage Account** - Shows storage account metrics including availability, latency, and transactions. -You can define and configure the data source in YAML files as part of Grafana's provisioning system. -For more information about provisioning, and for available configuration options, refer to [Provisioning Grafana](ref:provisioning-data-sources). +To import a pre-built dashboard: -#### Provisioning examples +1. Go to **Connections** > **Data sources**. +1. Select your Azure Monitor data source. +1. Click the **Dashboards** tab. +1. Click **Import** next to the dashboard you want to use. -**Azure AD App Registration (client secret):** +## Related resources -```yaml -apiVersion: 1 # config file version - -datasources: - - name: Azure Monitor - type: grafana-azure-monitor-datasource - access: proxy - jsonData: - azureAuthType: clientsecret - cloudName: azuremonitor # See table below - tenantId: - clientId: - subscriptionId: # Optional, default subscription - secureJsonData: - clientSecret: - version: 1 -``` - -**Managed Identity:** - -```yaml -apiVersion: 1 # config file version - -datasources: - - name: Azure Monitor - type: grafana-azure-monitor-datasource - access: proxy - jsonData: - azureAuthType: msi - subscriptionId: # Optional, default subscription - version: 1 -``` - -**Workload Identity:** - -```yaml -apiVersion: 1 # config file version - -datasources: - - name: Azure Monitor - type: grafana-azure-monitor-datasource - access: proxy - jsonData: - azureAuthType: workloadidentity - subscriptionId: # Optional, default subscription - version: 1 -``` - -**Current User:** - -{{< admonition type="note" >}} -The `oauthPassThru` property is required for current user authentication to function. -Additionally, `disableGrafanaCache` is necessary to prevent the data source returning cached responses for resources users don't have access to. -{{< /admonition >}} - -```yaml -apiVersion: 1 # config file version - -datasources: - - name: Azure Monitor - type: grafana-azure-monitor-datasource - access: proxy - jsonData: - azureAuthType: currentuser - oauthPassThru: true - disableGrafanaCache: true - subscriptionId: # Optional, default subscription - version: 1 -``` - -#### Supported cloud names - -| Azure Cloud | `cloudName` Value | -| ------------------------------------ | -------------------------- | -| **Microsoft Azure public cloud** | `azuremonitor` (_Default_) | -| **Microsoft Chinese national cloud** | `chinaazuremonitor` | -| **US Government cloud** | `govazuremonitor` | - -{{< admonition type="note" >}} -Cloud names for current user authentication differ to the `cloudName` values in the preceding table. -The public cloud name is `AzureCloud`, the Chinese national cloud name is `AzureChinaCloud`, and the US Government cloud name is `AzureUSGovernment`. -{{< /admonition >}} - -### Configure Managed Identity - -{{< admonition type="note" >}} -Managed Identity is available only in [Azure Managed Grafana](https://azure.microsoft.com/en-us/products/managed-grafana) or Grafana OSS/Enterprise when deployed in Azure. It is not available in Grafana Cloud. -{{< /admonition >}} - -You can use managed identity to configure Azure Monitor in Grafana if you host Grafana in Azure (such as an App Service or with Azure Virtual Machines) and have managed identity enabled on your VM. -This lets you securely authenticate data sources without manually configuring credentials via Azure AD App Registrations. -For details on Azure managed identities, refer to the [Azure documentation](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview). - -**To enable managed identity for Grafana:** - -1. Set the `managed_identity_enabled` flag in the `[azure]` section of the [Grafana server configuration](ref:configure-grafana-azure). - - ```ini - [azure] - managed_identity_enabled = true - ``` - -2. In the Azure Monitor data source configuration, set **Authentication** to **Managed Identity**. - - This hides the directory ID, application ID, and client secret fields, and the data source uses managed identity to authenticate to Azure Monitor Metrics and Logs, and Azure Resource Graph. - - {{< figure src="/media/docs/grafana/data-sources/screenshot-managed-identity-2.png" max-width="800px" class="docs-image--no-shadow" caption="Azure Monitor screenshot showing Managed Identity authentication" >}} - -3. You can set the `managed_identity_client_id` field in the `[azure]` section of the [Grafana server configuration](ref:configure-grafana-azure) to allow a user-assigned managed identity to be used instead of the default system-assigned identity. - -```ini -[azure] -managed_identity_enabled = true -managed_identity_client_id = USER_ASSIGNED_IDENTITY_CLIENT_ID -``` - -### Configure Workload Identity - -You can use workload identity to configure Azure Monitor in Grafana if you host Grafana in a Kubernetes environment, such as AKS, in conjunction with managed identities. -This lets you securely authenticate data sources without manually configuring credentials via Azure AD App Registrations. -For details on workload identity, refer to the [Azure workload identity documentation](https://azure.github.io/azure-workload-identity/docs/). - -**To enable workload identity for Grafana:** - -1. Set the `workload_identity_enabled` flag in the `[azure]` section of the [Grafana server configuration](ref:configure-grafana-azure). - - ```ini - [azure] - workload_identity_enabled = true - ``` - -2. In the Azure Monitor data source configuration, set **Authentication** to **Workload Identity**. - - This hides the directory ID, application ID, and client secret fields, and the data source uses workload identity to authenticate to Azure Monitor Metrics and Logs, and Azure Resource Graph. - - {{< figure src="/media/docs/grafana/data-sources/screenshot-workload-identity.png" max-width="800px" class="docs-image--no-shadow" caption="Azure Monitor screenshot showing Workload Identity authentication" >}} - -3. There are additional configuration variables that can control the authentication method.`workload_identity_tenant_id` represents the Azure AD tenant that contains the managed identity, `workload_identity_client_id` represents the client ID of the managed identity if it differs from the default client ID, `workload_identity_token_file` represents the path to the token file. Refer to the [documentation](https://azure.github.io/azure-workload-identity/docs/) for more information on what values these variables should use, if any. - - ```ini - [azure] - workload_identity_enabled = true - workload_identity_tenant_id = IDENTITY_TENANT_ID - workload_identity_client_id = IDENTITY_CLIENT_ID - workload_identity_token_file = TOKEN_FILE_PATH - ``` - -### Configure Current User authentication - -{{< admonition type="note" >}} -Current user authentication is an [experimental feature](/docs/release-life-cycle). Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Contact Grafana Support to enable this feature in Grafana Cloud. Aspects of Grafana may not work as expected when using this authentication method. -{{< /admonition >}} - -If your Grafana instance is configured with Azure Entra (formerly Active Directory) authentication for login, this authentication method can be used to forward the currently logged in user's credentials to the data source. The users credentials will then be used when requesting data from the data source. For details on how to configure your Grafana instance using Azure Entra refer to the [documentation](ref:configure-grafana-azure-auth). - -{{< admonition type="note" >}} -Additional configuration is required to ensure that the App Registration used to login a user via Azure provides an access token with the permissions required by the data source. - -The App Registration must be configured to issue both **Access Tokens** and **ID Tokens**. - -1. In the Azure Portal, open the App Registration that requires configuration. -2. Select **Authentication** in the side menu. -3. Under **Implicit grant and hybrid flows** check both the **Access tokens** and **ID tokens** boxes. -4. Save the changes to ensure the App Registration is updated. - -The App Registration must also be configured with additional **API Permissions** to provide authenticated users with access to the APIs utilised by the data source. - -1. In the Azure Portal, open the App Registration that requires configuration. -1. Select **API Permissions** in the side menu. -1. Ensure the `openid`, `profile`, `email`, and `offline_access` permissions are present under the **Microsoft Graph** section. If not, they must be added. -1. Select **Add a permission** and choose the following permissions. They must be added individually. Refer to the [Azure documentation](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-configure-app-access-web-apis) for more information. - - Select **Azure Service Management** > **Delegated permissions** > `user_impersonation` > **Add permissions** - - Select **APIs my organization uses** > Search for **Log Analytics API** and select it > **Delegated permissions** > `Date.Read` > **Add permissions** - -Once all permissions have been added, the Azure authentication section in Grafana must be updated. The `scopes` section must be updated to include the `.default` scope to ensure that a token with access to all APIs declared on the App Registration is requested by Grafana. Once updated the scopes value should equal: `.default openid email profile`. -{{< /admonition >}} - -This method of authentication doesn't inherently support all backend functionality as a user's credentials won't be in scope. -Affected functionality includes alerting, reporting, and recorded queries. -In order to support backend queries when using a data source configured with current user authentication, you can configure service credentials. -Also, note that query and resource caching is disabled by default for data sources using current user authentication. - -{{< admonition type="note" >}} -To configure fallback service credentials the [feature toggle](ref:configure-grafana-feature-toggles) `idForwarding` must be set to `true` and `user_identity_fallback_credentials_enabled` must be enabled in the [Azure configuration section](ref:configure-grafana-azure) (enabled by default when `user_identity_enabled` is set to `true`). -{{< /admonition >}} - -Permissions for fallback credentials may need to be broad to appropriately support backend functionality. -For example, an alerting query created by a user is dependent on their permissions. -If a user tries to create an alert for a resource that the fallback credentials can't access, the alert will fail. - -**To enable current user authentication for Grafana:** - -1. Set the `user_identity_enabled` flag in the `[azure]` section of the [Grafana server configuration](ref:configure-grafana-azure). - By default this will also enable fallback service credentials. - If you want to disable service credentials at the instance level set `user_identity_fallback_credentials_enabled` to false. - - ```ini - [azure] - user_identity_enabled = true - ``` - -1. In the Azure Monitor data source configuration, set **Authentication** to **Current User**. - If fallback service credentials are enabled at the instance level, an additional configuration section is visible that you can use to enable or disable using service credentials for this data source. - {{< figure src="/media/docs/grafana/data-sources/screenshot-current-user.png" max-width="800px" class="docs-image--no-shadow" caption="Azure Monitor screenshot showing Current User authentication" >}} - -1. If you want backend functionality to work with this data source, enable service credentials and configure the data source using the most applicable credentials for your circumstances. - -## Query the data source - -The Azure Monitor data source can query data from Azure Monitor Metrics and Logs, the Azure Resource Graph, and Application Insights Traces. Each source has its own specialized query editor. - -For details, see the [query editor documentation](query-editor/). - -## Use template variables - -Instead of hard-coding details such as server, application, and sensor names in metric queries, you can use variables. -Grafana lists these variables in dropdown select boxes at the top of the dashboard to help you change the data displayed in your dashboard. -Grafana refers to such variables as template variables. - -For details, see the [template variables documentation](template-variables/). - -## Application Insights and Insights Analytics (removed) - -Until Grafana v8.0, you could query the same Azure Application Insights data using Application Insights and Insights Analytics. - -These queries were deprecated in Grafana v7.5. In Grafana v8.0, Application Insights and Insights Analytics were made read-only in favor of querying this data through Metrics and Logs. These query methods were completely removed in Grafana v9.0. - -If you're upgrading from a Grafana version prior to v9.0 and relied on Application Insights and Analytics queries, refer to the [Grafana v9.0 documentation](/docs/grafana/v9.0/datasources/azuremonitor/deprecated-application-insights/) for help migrating these queries to Metrics and Logs queries. +- [Azure Monitor documentation](https://docs.microsoft.com/en-us/azure/azure-monitor/) +- [Kusto Query Language (KQL) reference](https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/) +- [Grafana community forum](https://community.grafana.com/) diff --git a/docs/sources/datasources/azure-monitor/alerting/index.md b/docs/sources/datasources/azure-monitor/alerting/index.md new file mode 100644 index 00000000000..860c1d343a4 --- /dev/null +++ b/docs/sources/datasources/azure-monitor/alerting/index.md @@ -0,0 +1,262 @@ +--- +aliases: + - ../../data-sources/azure-monitor/alerting/ +description: Set up alerts using Azure Monitor data in Grafana +keywords: + - grafana + - azure + - monitor + - alerting + - alerts + - metrics + - logs +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Alerting +title: Azure Monitor alerting +weight: 500 +refs: + alerting: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//alerting/ + alerting-fundamentals: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/fundamentals/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//alerting/fundamentals/ + create-alert-rule: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/alerting-rules/create-grafana-managed-rule/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//alerting/alerting-rules/create-grafana-managed-rule/ + grafana-managed-recording-rules: + - pattern: /docs/grafana/ + destination: /docs/grafana//alerting/alerting-rules/create-recording-rules/create-grafana-managed-recording-rules/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//alerting/alerting-rules/create-recording-rules/create-grafana-managed-recording-rules/ + configure-azure-monitor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/configure/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/configure/ + query-editor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/query-editor/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/query-editor/ + troubleshoot: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/troubleshooting/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/troubleshooting/ +--- + +# Azure Monitor alerting + +The Azure Monitor data source supports [Grafana Alerting](ref:alerting) and [Grafana-managed recording rules](ref:grafana-managed-recording-rules), allowing you to create alert rules based on Azure metrics, logs, traces, and resource data. You can monitor your Azure environment and receive notifications when specific conditions are met. + +## Before you begin + +- Ensure you have the appropriate permissions to create alert rules in Grafana. +- Verify your Azure Monitor data source is configured and working correctly. +- Familiarize yourself with [Grafana Alerting concepts](ref:alerting-fundamentals). +- **Important**: Verify your data source uses a supported authentication method. Refer to [Authentication requirements](#authentication-requirements). + +## Supported query types for alerting + +All Azure Monitor query types support alerting and recording rules: + +| Query type | Use case | Notes | +| -------------------- | -------------------------------------------------- | -------------------------------------------------------- | +| Metrics | Threshold-based alerts on Azure resource metrics | Best suited for alerting; returns time-series data | +| Logs | Alert on log patterns, error counts, or thresholds | Use KQL to aggregate data into numeric values | +| Azure Resource Graph | Alert on resource state or configuration changes | Use count aggregations to return numeric data | +| Traces | Alert on trace data and application performance | Use aggregations to return numeric values for evaluation | + +{{< admonition type="note" >}} +Alert queries must return numeric data that Grafana can evaluate against a threshold. Queries that return only text or non-numeric data cannot be used directly for alerting. +{{< /admonition >}} + +## Authentication requirements + +Alerting and recording rules run as background processes without a user context. This means they require service-level authentication and don't work with all authentication methods. + +| Authentication method | Supported | +| -------------------------------- | ------------------------------------- | +| App Registration (client secret) | ✓ | +| Managed Identity | ✓ | +| Workload Identity | ✓ | +| Current User | ✓ (with fallback service credentials) | + +{{< admonition type="note" >}} +If you use **Current User** authentication, you must configure **fallback service credentials** for alerting and recording rules to function. User credentials aren't available for background operations, so Grafana uses the fallback credentials instead. Refer to [configure the data source](ref:configure-azure-monitor) for details on setting up fallback credentials. +{{< /admonition >}} + +## Create an alert rule + +To create an alert rule using Azure Monitor data: + +1. Go to **Alerting** > **Alert rules**. +1. Click **New alert rule**. +1. Enter a name for your alert rule. +1. In the **Define query and alert condition** section: + - Select your Azure Monitor data source. + - Configure your query (for example, a Metrics query for CPU usage or a Logs query using KQL). + - Add a **Reduce** expression if your query returns multiple series. + - Add a **Threshold** expression to define the alert condition. +1. Configure the **Set evaluation behavior**: + - Select or create a folder and evaluation group. + - Set the evaluation interval (how often the alert is checked). + - Set the pending period (how long the condition must be true before firing). +1. Add labels and annotations to provide context for notifications. +1. Click **Save rule**. + +For detailed instructions, refer to [Create a Grafana-managed alert rule](ref:create-alert-rule). + +## Example: VM CPU usage alert + +This example creates an alert that fires when virtual machine CPU usage exceeds 80%: + +1. Create a new alert rule. +1. Configure the query: + - **Service**: Metrics + - **Resource**: Select your virtual machine + - **Metric namespace**: `Microsoft.Compute/virtualMachines` + - **Metric**: `Percentage CPU` + - **Aggregation**: `Average` +1. Add expressions: + - **Reduce**: Last (to get the most recent data point) + - **Threshold**: Is above 80 +1. Set evaluation to run every 1 minute with a 5-minute pending period. +1. Save the rule. + +## Example: Error log count alert + +This example alerts when error logs exceed a threshold using a KQL query: + +1. Create a new alert rule. +1. Configure the query: + - **Service**: Logs + - **Resource**: Select your Log Analytics workspace + - **Query**: + ```kusto + AppExceptions + | where TimeGenerated > ago(5m) + | summarize ErrorCount = count() by bin(TimeGenerated, 1m) + ``` +1. Add expressions: + - **Reduce**: Max (to get the highest count in the period) + - **Threshold**: Is above 10 +1. Set evaluation to run every 5 minutes. +1. Save the rule. + +## Example: Resource count alert + +This example alerts when the number of running virtual machines drops below a threshold using Azure Resource Graph: + +1. Create a new alert rule. +1. Configure the query: + - **Service**: Azure Resource Graph + - **Subscriptions**: Select your subscriptions + - **Query**: + + ```kusto + resources + | where type == "microsoft.compute/virtualmachines" + | where properties.extended.instanceView.powerState.displayStatus == "VM running" + | summarize RunningVMs = count() + ``` + +1. Add expressions: + - **Reduce**: Last + - **Threshold**: Is below 3 +1. Set evaluation to run every 5 minutes. +1. Save the rule. + +## Best practices + +Follow these recommendations to create reliable and efficient alerts with Azure Monitor data. + +### Use appropriate query intervals + +- Set the alert evaluation interval to be greater than or equal to the minimum data resolution from Azure Monitor. +- Azure Monitor Metrics typically have 1-minute granularity at minimum. +- Avoid very short intervals (less than 1 minute) as they may cause evaluation timeouts or miss data points. + +### Reduce multiple series + +When your Azure Monitor query returns multiple time series (for example, CPU usage across multiple VMs), use the **Reduce** expression to aggregate them: + +- **Last**: Use the most recent value +- **Mean**: Average across all series +- **Max/Min**: Use the highest or lowest value +- **Sum**: Total across all series + +### Optimize Log Analytics queries + +For Logs queries used in alerting: + +- Use `summarize` to aggregate data into numeric values. +- Include appropriate time filters using `ago()` or `TimeGenerated`. +- Avoid returning large result sets; aggregate data in the query. +- Test queries in Explore before using them in alert rules. + +### Handle no data conditions + +Configure what happens when no data is returned: + +1. In the alert rule, find **Configure no data and error handling**. +1. Choose an appropriate action: + - **No Data**: Keep the alert in its current state + - **Alerting**: Treat no data as an alert condition + - **OK**: Treat no data as a healthy state + +### Test queries before alerting + +Always verify your query returns expected data before creating an alert: + +1. Go to **Explore**. +1. Select your Azure Monitor data source. +1. Run the query you plan to use for alerting. +1. Confirm the data format and values are correct. +1. Verify the query returns numeric data suitable for threshold evaluation. + +## Troubleshooting + +If your Azure Monitor alerts aren't working as expected, use the following sections to diagnose and resolve common issues. + +### Alerts not firing + +- Verify the data source uses a supported authentication method. If using Current User authentication, ensure fallback service credentials are configured. +- Check that the query returns numeric data in Explore. +- Ensure the evaluation interval allows enough time for data to be available. +- Review the alert rule's health and any error messages in the Alerting UI. + +### Authentication errors in alert evaluation + +If you see authentication errors when alerts evaluate: + +- Confirm the data source is configured with App Registration, Managed Identity, Workload Identity, or Current User with fallback service credentials. +- If using App Registration, verify the client secret hasn't expired. +- If using Current User, verify that fallback service credentials are configured and valid. +- Check that the service principal has appropriate permissions on Azure resources. + +### Query timeout errors + +- Simplify complex KQL queries. +- Reduce the time range in Log Analytics queries. +- Add more specific filters to narrow result sets. + +For additional troubleshooting help, refer to [Troubleshoot Azure Monitor](ref:troubleshoot). + +## Additional resources + +- [Grafana Alerting documentation](ref:alerting) +- [Create alert rules](ref:create-alert-rule) +- [Azure Monitor query editor](ref:query-editor) +- [Grafana-managed recording rules](ref:grafana-managed-recording-rules) diff --git a/docs/sources/datasources/azure-monitor/annotations/index.md b/docs/sources/datasources/azure-monitor/annotations/index.md new file mode 100644 index 00000000000..43fbb914a9d --- /dev/null +++ b/docs/sources/datasources/azure-monitor/annotations/index.md @@ -0,0 +1,218 @@ +--- +aliases: + - ../../data-sources/azure-monitor/annotations/ +description: Use annotations with the Azure Monitor data source in Grafana +keywords: + - grafana + - azure + - monitor + - annotations + - events + - logs +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Annotations +title: Azure Monitor annotations +weight: 450 +refs: + annotate-visualizations: + - pattern: /docs/grafana/ + destination: /docs/grafana//dashboards/build-dashboards/annotate-visualizations/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//dashboards/build-dashboards/annotate-visualizations/ + query-editor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/query-editor/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/query-editor/ +--- + +# Azure Monitor annotations + +[Annotations](ref:annotate-visualizations) overlay rich event information on top of graphs. You can use Azure Monitor Log Analytics queries to create annotations that mark important events, deployments, alerts, or other significant occurrences on your dashboards. + +## Before you begin + +- Ensure you have configured the Azure Monitor data source. +- You need access to a Log Analytics workspace containing the data you want to use for annotations. +- Annotations use Log Analytics (KQL) queries only. Metrics, Traces, and Azure Resource Graph queries are not supported for annotations. + +## Create an annotation query + +To add an Azure Monitor annotation to a dashboard: + +1. Open the dashboard where you want to add annotations. +1. Click **Dashboard settings** (gear icon) in the top navigation. +1. Select **Annotations** in the left menu. +1. Click **Add annotation query**. +1. Enter a **Name** for the annotation (e.g., "Azure Activity", "Deployments"). +1. Select your **Azure Monitor** data source. +1. Choose the **Logs** service. +1. Select a **Resource** (Log Analytics workspace or Application Insights resource). +1. Write a KQL query that returns the annotation data. +1. Click **Apply** to save. + +## Query requirements + +Your KQL query should return columns that Grafana can use to create annotations: + +| Column | Required | Description | +| ------------------ | ----------- | ------------------------------------------------------------------------------------------------ | +| `TimeGenerated` | Yes | The timestamp for the annotation. Grafana uses this to position the annotation on the time axis. | +| `Text` | Recommended | The annotation text displayed when you hover over or click the annotation. | +| Additional columns | Optional | Any other columns returned become annotation tags. | + +{{< admonition type="note" >}} +Always include a time filter in your query to limit results to the dashboard's time range. Use the `$__timeFilter()` macro. +{{< /admonition >}} + +## Annotation query examples + +The following examples demonstrate common annotation use cases. + +### Azure Activity Log events + +Display Azure Activity Log events such as resource modifications, deployments, and administrative actions: + +```kusto +AzureActivity +| where $__timeFilter(TimeGenerated) +| where Level == "Error" or Level == "Warning" or CategoryValue == "Administrative" +| project TimeGenerated, Text=OperationNameValue, Level, ResourceGroup, Caller +| order by TimeGenerated desc +| take 100 +``` + +### Deployment events + +Show deployment-related activity: + +```kusto +AzureActivity +| where $__timeFilter(TimeGenerated) +| where OperationNameValue contains "deployments" +| project TimeGenerated, Text=strcat("Deployment: ", OperationNameValue), Status=ActivityStatusValue, ResourceGroup +| order by TimeGenerated desc +``` + +### Application Insights exceptions + +Mark application exceptions as annotations: + +```kusto +AppExceptions +| where $__timeFilter(TimeGenerated) +| project TimeGenerated, Text=strcat(ProblemId, ": ", OuterMessage), SeverityLevel, AppRoleName +| order by TimeGenerated desc +| take 50 +``` + +### Custom events from Application Insights + +Display custom events logged by your application: + +```kusto +AppEvents +| where $__timeFilter(TimeGenerated) +| where Name == "DeploymentStarted" or Name == "DeploymentCompleted" +| project TimeGenerated, Text=Name, AppRoleName +| order by TimeGenerated desc +``` + +### Security alerts + +Show security-related alerts: + +```kusto +SecurityAlert +| where $__timeFilter(TimeGenerated) +| project TimeGenerated, Text=AlertName, Severity=AlertSeverity, Description +| order by TimeGenerated desc +| take 50 +``` + +### Resource health events + +Display resource health status changes: + +```kusto +AzureActivity +| where $__timeFilter(TimeGenerated) +| where CategoryValue == "ResourceHealth" +| project TimeGenerated, Text=OperationNameValue, Status=ActivityStatusValue, ResourceId +| order by TimeGenerated desc +``` + +### VM start and stop events + +Mark virtual machine state changes: + +```kusto +AzureActivity +| where $__timeFilter(TimeGenerated) +| where OperationNameValue has_any ("start", "deallocate", "restart") +| where ResourceProviderValue == "MICROSOFT.COMPUTE" +| project TimeGenerated, Text=OperationNameValue, VM=Resource, Status=ActivityStatusValue +| order by TimeGenerated desc +``` + +### Autoscale events + +Show autoscale operations: + +```kusto +AzureActivity +| where $__timeFilter(TimeGenerated) +| where OperationNameValue contains "autoscale" +| project TimeGenerated, Text=strcat("Autoscale: ", OperationNameValue), Status=ActivityStatusValue, ResourceGroup +| order by TimeGenerated desc +``` + +## Customize annotation appearance + +After creating an annotation query, you can customize its appearance: + +| Setting | Description | +| ------------- | -------------------------------------------------------------------------------------------------------- | +| **Color** | Choose a color for the annotation markers. Use different colors to distinguish between annotation types. | +| **Show in** | Select which panels display the annotations. | +| **Filter by** | Add filters to limit when annotations appear. | + +## Best practices + +Follow these recommendations when creating annotations: + +1. **Limit results**: Always use `take` or `limit` to restrict the number of annotations. Too many annotations can clutter your dashboard and impact performance. + +2. **Use time filters**: Include `$__timeFilter()` to ensure queries only return data within the dashboard's time range. + +3. **Create meaningful text**: Use `strcat()` or `project` to create descriptive annotation text that provides context at a glance. + +4. **Add relevant tags**: Include columns like `ResourceGroup`, `Severity`, or `Status` that become clickable tags for filtering. + +5. **Use descriptive names**: Name your annotations clearly (e.g., "Production Deployments", "Critical Alerts") so dashboard users understand what they represent. + +## Troubleshoot annotations + +If annotations aren't appearing as expected, try the following solutions. + +### Annotations don't appear + +- Verify the query returns data in the selected time range. +- Check that the query includes a `TimeGenerated` column. +- Test the query in the Azure Portal Log Analytics query editor. +- Ensure the annotation is enabled (toggle is on). + +### Too many annotations + +- Add more specific filters to your query. +- Use `take` to limit results. +- Narrow the time range. + +### Annotations appear at wrong times + +- Verify the `TimeGenerated` column contains the correct timestamp. +- Check your dashboard's timezone settings. diff --git a/docs/sources/datasources/azure-monitor/configure/index.md b/docs/sources/datasources/azure-monitor/configure/index.md new file mode 100644 index 00000000000..cef21b08744 --- /dev/null +++ b/docs/sources/datasources/azure-monitor/configure/index.md @@ -0,0 +1,605 @@ +--- +aliases: + - ../../data-sources/azure-monitor/configure/ +description: Guide for configuring the Azure Monitor data source in Grafana. +keywords: + - grafana + - microsoft + - azure + - monitor + - application + - insights + - log + - analytics + - guide +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Configure +title: Configure the Azure Monitor data source +weight: 200 +last_reviewed: 2025-12-04 +refs: + configure-grafana-feature-toggles: + - pattern: /docs/grafana/ + destination: /docs/grafana//setup-grafana/configure-grafana/#feature_toggles + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//setup-grafana/configure-grafana/#feature_toggles + provisioning-data-sources: + - pattern: /docs/grafana/ + destination: /docs/grafana//administration/provisioning/#data-sources + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//administration/provisioning/#data-sources + explore: + - pattern: /docs/grafana/ + destination: /docs/grafana//explore/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//explore/ + configure-grafana-azure-auth: + - pattern: /docs/grafana/ + destination: /docs/grafana//setup-grafana/configure-security/configure-authentication/azuread/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//setup-grafana/configure-security/configure-authentication/azuread/ + build-dashboards: + - pattern: /docs/grafana/ + destination: /docs/grafana//dashboards/build-dashboards/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//dashboards/build-dashboards/ + configure-grafana-azure: + - pattern: /docs/grafana/ + destination: /docs/grafana//setup-grafana/configure-grafana/#azure + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//setup-grafana/configure-grafana/#azure + data-source-management: + - pattern: /docs/grafana/ + destination: /docs/grafana//administration/data-source-management/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//administration/data-source-management/ + configure-grafana-azure-auth-scopes: + - pattern: /docs/grafana/ + destination: /docs/grafana//setup-grafana/configure-security/configure-authentication/azuread/#enable-azure-ad-oauth-in-grafana + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//setup-grafana/configure-security/configure-authentication/azuread/#enable-azure-ad-oauth-in-grafana + data-sources: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/ + private-data-source-connect: + - pattern: /docs/grafana/ + destination: docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/ + - pattern: /docs/grafana-cloud/ + destination: docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/ + configure-pdc: + - pattern: /docs/grafana/ + destination: /docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/configure-pdc/#configure-grafana-private-data-source-connect-pdc + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/configure-pdc/#configure-grafana-private-data-source-connect-pdc +--- + +# Configure the Azure Monitor data source + +This document explains how to configure the Azure Monitor data source and the available configuration options. +For general information about data sources, refer to [Grafana data sources](ref:data-sources) and [Data source management](ref:data-source-management). + +## Before you begin + +Before configuring the Azure Monitor data source, ensure you have the following: + +- **Grafana permissions:** You must have the `Organization administrator` role to configure data sources. + Organization administrators can also [configure the data source via YAML](#provision-the-data-source) with the Grafana provisioning system or [using Terraform](#configure-with-terraform). + +- **Azure prerequisites:** Depending on your chosen authentication method, you may need: + - A Microsoft Entra ID (formerly Azure AD) app registration with a service principal (for App Registration authentication) + - A Managed Identity enabled on your Azure VM or App Service (for Managed Identity authentication) + - Workload identity configured in your Kubernetes cluster (for Workload Identity authentication) + - Microsoft Entra ID authentication configured for Grafana login (for Current User authentication) + +{{< admonition type="note" >}} +**Grafana Cloud users:** Managed Identity and Workload Identity authentication methods are not available in Grafana Cloud because they require Grafana to run on your Azure infrastructure. Use **App Registration** authentication instead. +{{< /admonition >}} + +- **Azure RBAC permissions:** The identity used to authenticate must have the `Reader` role on the Azure subscription containing the resources you want to monitor. + For Log Analytics queries, the identity also needs appropriate permissions on the Log Analytics workspaces to be queried. + Refer to the [Azure documentation for role assignments](https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal?tabs=current). + +{{< admonition type="note" >}} +The Azure Monitor data source plugin is built into Grafana. No additional installation is required. +{{< /admonition >}} + +## Add the data source + +To add the Azure Monitor data source: + +1. Click **Connections** in the left-side menu. +1. Click **Add new connection**. +1. Type `Azure Monitor` in the search bar. +1. Select **Azure Monitor**. +1. Click **Add new data source** in the upper right. + +You're taken to the **Settings** tab where you can configure the data source. + +## Choose an authentication method + +The Azure Monitor data source supports four authentication methods. Choose based on where Grafana is hosted and your security requirements: + +| Authentication method | Best for | Requirements | +| --------------------- | ------------------------------------------ | -------------------------------------------------------------- | +| **App Registration** | Any Grafana deployment | Microsoft Entra ID app registration with client secret | +| **Managed Identity** | Grafana hosted in Azure (VMs, App Service) | Managed identity enabled on the Azure resource | +| **Workload Identity** | Grafana in Kubernetes (AKS) | Workload identity federation configured | +| **Current User** | User-level access control | Microsoft Entra ID authentication configured for Grafana login | + +## Configure authentication + +Select one of the following authentication methods and complete the configuration. + +### App Registration + +Use a Microsoft Entra ID app registration (service principal) to authenticate. This method works with any Grafana deployment. + +#### App Registration prerequisites + +1. Create an app registration in Microsoft Entra ID. + Refer to the [Azure documentation for creating a service principal](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#get-tenant-and-app-id-values-for-signing-in). + +1. Create a client secret for the app registration. + Refer to the [Azure documentation for creating a client secret](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal#option-2-create-a-new-application-secret). + +1. Assign the `Reader` role to the app registration on the subscription or resources you want to monitor. + Refer to the [Azure documentation for role assignments](https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal?tabs=current). + +#### App Registration UI configuration + +| Setting | Description | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| **Authentication** | Select **App Registration**. | +| **Azure Cloud** | The Azure environment to connect to. Select **Azure** for the public cloud, or choose Azure Government or Azure China for national clouds. | +| **Directory (tenant) ID** | The GUID that identifies your Microsoft Entra ID tenant. | +| **Application (client) ID** | The GUID for the app registration you created. | +| **Client secret** | The secret key for the app registration. Keep this secure and rotate periodically. | +| **Default Subscription** | Click **Load Subscriptions** to populate available subscriptions, then select your default. | + +#### Provision App Registration with YAML + +```yaml +apiVersion: 1 + +datasources: + - name: Azure Monitor + type: grafana-azure-monitor-datasource + access: proxy + jsonData: + azureAuthType: clientsecret + cloudName: azuremonitor # See supported cloud names below + tenantId: + clientId: + subscriptionId: # Optional, default subscription + secureJsonData: + clientSecret: + version: 1 +``` + +### Managed Identity + +Use Azure Managed Identity for secure, credential-free authentication when Grafana is hosted in Azure. + +{{< admonition type="note" >}} +Managed Identity is available in [Azure Managed Grafana](https://azure.microsoft.com/en-us/products/managed-grafana) or self-hosted Grafana deployed in Azure. It is not available in Grafana Cloud. +{{< /admonition >}} + +#### Managed Identity prerequisites + +- Grafana must be hosted in Azure (App Service, Azure VMs, or Azure Managed Grafana). +- Managed identity must be enabled on the Azure resource hosting Grafana. +- The managed identity must have the `Reader` role on the subscription or resources you want to monitor. + +For details on Azure managed identities, refer to the [Azure documentation](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview). + +#### Managed Identity Grafana server configuration + +Enable managed identity in the Grafana server configuration: + +```ini +[azure] +managed_identity_enabled = true +``` + +To use a user-assigned managed identity instead of the system-assigned identity, also set: + +```ini +[azure] +managed_identity_enabled = true +managed_identity_client_id = +``` + +Refer to [Grafana Azure configuration](ref:configure-grafana-azure) for more details. + +#### Managed Identity UI configuration + +| Setting | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------- | +| **Authentication** | Select **Managed Identity**. The directory ID, application ID, and client secret fields are hidden. | +| **Default Subscription** | Click **Load Subscriptions** to populate available subscriptions, then select your default. | + +{{< figure src="/media/docs/grafana/data-sources/screenshot-managed-identity-2.png" max-width="800px" class="docs-image--no-shadow" caption="Azure Monitor data source configured with Managed Identity" >}} + +#### Provision Managed Identity with YAML + +```yaml +apiVersion: 1 + +datasources: + - name: Azure Monitor + type: grafana-azure-monitor-datasource + access: proxy + jsonData: + azureAuthType: msi + subscriptionId: # Optional, default subscription + version: 1 +``` + +### Workload Identity + +Use Azure Workload Identity for secure authentication in Kubernetes environments like AKS. + +#### Workload Identity prerequisites + +- Grafana must be running in a Kubernetes environment with workload identity federation configured. +- The workload identity must have the `Reader` role on the subscription or resources you want to monitor. + +For details, refer to the [Azure workload identity documentation](https://azure.github.io/azure-workload-identity/docs/). + +#### Workload Identity Grafana server configuration + +Enable workload identity in the Grafana server configuration: + +```ini +[azure] +workload_identity_enabled = true +``` + +Optional configuration variables: + +```ini +[azure] +workload_identity_enabled = true +workload_identity_tenant_id = # Microsoft Entra ID tenant containing the managed identity +workload_identity_client_id = # Client ID if different from default +workload_identity_token_file = # Path to the token file +``` + +Refer to [Grafana Azure configuration](ref:configure-grafana-azure) and the [Azure workload identity documentation](https://azure.github.io/azure-workload-identity/docs/) for more details. + +#### Workload Identity UI configuration + +| Setting | Description | +| ------------------------ | ---------------------------------------------------------------------------------------------------- | +| **Authentication** | Select **Workload Identity**. The directory ID, application ID, and client secret fields are hidden. | +| **Default Subscription** | Click **Load Subscriptions** to populate available subscriptions, then select your default. | + +{{< figure src="/media/docs/grafana/data-sources/screenshot-workload-identity.png" max-width="800px" class="docs-image--no-shadow" caption="Azure Monitor data source configured with Workload Identity" >}} + +#### Provision Workload Identity with YAML + +```yaml +apiVersion: 1 + +datasources: + - name: Azure Monitor + type: grafana-azure-monitor-datasource + access: proxy + jsonData: + azureAuthType: workloadidentity + subscriptionId: # Optional, default subscription + version: 1 +``` + +### Current User + +Forward the logged-in Grafana user's Azure credentials to the data source for user-level access control. + +{{< admonition type="warning" >}} +Current User authentication is an [experimental feature](/docs/release-life-cycle/). Engineering and on-call support is not available. Documentation is limited. No SLA is provided. Contact Grafana Support to enable this feature in Grafana Cloud. +{{< /admonition >}} + +#### Current User prerequisites + +Your Grafana instance must be configured with Microsoft Entra ID authentication. Refer to the [Microsoft Entra ID authentication documentation](ref:configure-grafana-azure-auth). + +#### Configure your Azure App Registration + +The App Registration used for Grafana login requires additional configuration: + +**Enable token issuance:** + +1. In the Azure Portal, open your App Registration. +1. Select **Authentication** in the side menu. +1. Under **Implicit grant and hybrid flows**, check both **Access tokens** and **ID tokens**. +1. Save your changes. + +**Add API permissions:** + +1. In the Azure Portal, open your App Registration. +1. Select **API Permissions** in the side menu. +1. Ensure these permissions are present under **Microsoft Graph**: `openid`, `profile`, `email`, and `offline_access`. +1. Add the following permissions: + - **Azure Service Management** > **Delegated permissions** > `user_impersonation` + - **APIs my organization uses** > Search for **Log Analytics API** > **Delegated permissions** > `Data.Read` + +Refer to the [Azure documentation](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-configure-app-access-web-apis) for more information. + +**Update Grafana scopes:** + +Update the `scopes` section in your Grafana Azure authentication configuration to include the `.default` scope: + +``` +.default openid email profile +``` + +#### Current User Grafana server configuration + +Enable current user authentication in the Grafana server configuration: + +```ini +[azure] +user_identity_enabled = true +``` + +By default, this also enables fallback service credentials. To disable fallback credentials at the instance level: + +```ini +[azure] +user_identity_enabled = true +user_identity_fallback_credentials_enabled = false +``` + +{{< admonition type="note" >}} +To use fallback service credentials, the [feature toggle](ref:configure-grafana-feature-toggles) `idForwarding` must be set to `true`. +{{< /admonition >}} + +#### Limitations and fallback credentials + +Current User authentication doesn't support backend functionality like alerting, reporting, and recorded queries because user credentials aren't available for background operations. + +To support these features, configure **fallback service credentials**. When enabled, Grafana uses the fallback credentials for backend operations. Note that operations using fallback credentials are limited to the permissions of those credentials, not the user's permissions. + +{{< admonition type="note" >}} +Query and resource caching is disabled by default for data sources using Current User authentication. +{{< /admonition >}} + +#### Current User UI configuration + +| Setting | Description | +| -------------------------------- | ------------------------------------------------------------------------------------------- | +| **Authentication** | Select **Current User**. | +| **Default Subscription** | Click **Load Subscriptions** to populate available subscriptions, then select your default. | +| **Fallback Service Credentials** | Enable and configure credentials for backend features like alerting. | + +{{< figure src="/media/docs/grafana/data-sources/screenshot-current-user.png" max-width="800px" class="docs-image--no-shadow" caption="Azure Monitor data source configured with Current User authentication" >}} + +#### Provision Current User with YAML + +{{< admonition type="note" >}} +The `oauthPassThru` property is required for Current User authentication. The `disableGrafanaCache` property prevents returning cached responses for resources users don't have access to. +{{< /admonition >}} + +```yaml +apiVersion: 1 + +datasources: + - name: Azure Monitor + type: grafana-azure-monitor-datasource + access: proxy + jsonData: + azureAuthType: currentuser + oauthPassThru: true + disableGrafanaCache: true + subscriptionId: # Optional, default subscription + version: 1 +``` + +## Additional configuration options + +These settings apply to all authentication methods. + +### General settings + +| Setting | Description | +| ----------- | ------------------------------------------------------------------------------- | +| **Name** | The data source name used in panels and queries. Example: `azure-monitor-prod`. | +| **Default** | Toggle to make this the default data source for new panels. | + +### Enable Basic Logs + +Toggle **Enable Basic Logs** to allow queries against [Basic Logs tables](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/basic-logs-query?tabs=portal-1) in supported Log Analytics Workspaces. + +{{< admonition type="note" >}} +Querying Basic Logs tables incurs additional costs on a per-query basis. +{{< /admonition >}} + +### Private data source connect (Grafana Cloud only) + +If you're using Grafana Cloud and need to connect to Azure resources in a private network, use Private Data Source Connect (PDC). + +1. Click the **Private data source connect** dropdown to select your PDC configuration. +1. Click **Manage private data source connect** to view your PDC connection details. + +For more information, refer to [Private data source connect](ref:private-data-source-connect) and [Configure PDC](ref:configure-pdc). + +## Supported cloud names + +When provisioning the data source, use the following `cloudName` values: + +| Azure Cloud | `cloudName` value | +| -------------------------------- | ------------------------ | +| Microsoft Azure public cloud | `azuremonitor` (default) | +| Microsoft Chinese national cloud | `chinaazuremonitor` | +| US Government cloud | `govazuremonitor` | + +{{< admonition type="note" >}} +For Current User authentication, the cloud names differ: use `AzureCloud` for public cloud, `AzureChinaCloud` for the Chinese national cloud, and `AzureUSGovernment` for the US Government cloud. +{{< /admonition >}} + +## Verify the connection + +After configuring the data source, click **Save & test**. A successful connection displays a message confirming that the credentials are valid and have access to the configured default subscription. + +If the test fails, verify: + +- Your credentials are correct (tenant ID, client ID, client secret) +- The identity has the required Azure RBAC permissions +- For Managed Identity or Workload Identity, that the Grafana server configuration is correct +- Network connectivity to Azure endpoints + +## Provision the data source + +You can define and configure the Azure Monitor data source in YAML files as part of the Grafana provisioning system. +For more information about provisioning, refer to [Provisioning Grafana](ref:provisioning-data-sources). + +### Provision quick reference + +| Authentication method | `azureAuthType` value | Required fields | +| --------------------- | --------------------- | -------------------------------------------------- | +| App Registration | `clientsecret` | `tenantId`, `clientId`, `clientSecret` | +| Managed Identity | `msi` | None (uses VM identity) | +| Workload Identity | `workloadidentity` | None (uses pod identity) | +| Current User | `currentuser` | `oauthPassThru: true`, `disableGrafanaCache: true` | + +All methods support the optional `subscriptionId` field to set a default subscription. + +For complete YAML examples, see the [authentication method sections](#configure-authentication) above. + +## Configure with Terraform + +You can configure the Azure Monitor data source using the [Grafana Terraform provider](https://registry.terraform.io/providers/grafana/grafana/latest/docs). This approach enables infrastructure-as-code workflows and version control for your Grafana configuration. + +### Terraform prerequisites + +- [Terraform](https://www.terraform.io/downloads) installed +- Grafana Terraform provider configured with appropriate credentials +- For Grafana Cloud: A [Cloud Access Policy token](https://grafana.com/docs/grafana-cloud/account-management/authentication-and-permissions/access-policies/) with data source permissions + +### Provider configuration + +Configure the Grafana provider to connect to your Grafana instance: + +```hcl +terraform { + required_providers { + grafana = { + source = "grafana/grafana" + version = ">= 2.0.0" + } + } +} + +# For Grafana Cloud +provider "grafana" { + url = "" + auth = "" +} + +# For self-hosted Grafana +# provider "grafana" { +# url = "http://localhost:3000" +# auth = "" +# } +``` + +### Terraform examples + +The following examples show how to configure the Azure Monitor data source for each authentication method. + +**App Registration (client secret):** + +```hcl +resource "grafana_data_source" "azure_monitor" { + type = "grafana-azure-monitor-datasource" + name = "Azure Monitor" + + json_data_encoded = jsonencode({ + azureAuthType = "clientsecret" + cloudName = "azuremonitor" + tenantId = "" + clientId = "" + subscriptionId = "" + }) + + secure_json_data_encoded = jsonencode({ + clientSecret = "" + }) +} +``` + +**Managed Identity:** + +```hcl +resource "grafana_data_source" "azure_monitor" { + type = "grafana-azure-monitor-datasource" + name = "Azure Monitor" + + json_data_encoded = jsonencode({ + azureAuthType = "msi" + subscriptionId = "" + }) +} +``` + +**Workload Identity:** + +```hcl +resource "grafana_data_source" "azure_monitor" { + type = "grafana-azure-monitor-datasource" + name = "Azure Monitor" + + json_data_encoded = jsonencode({ + azureAuthType = "workloadidentity" + subscriptionId = "" + }) +} +``` + +**Current User:** + +```hcl +resource "grafana_data_source" "azure_monitor" { + type = "grafana-azure-monitor-datasource" + name = "Azure Monitor" + + json_data_encoded = jsonencode({ + azureAuthType = "currentuser" + oauthPassThru = true + disableGrafanaCache = true + subscriptionId = "" + }) +} +``` + +**With Basic Logs enabled:** + +Add `enableBasicLogs = true` to any of the above configurations: + +```hcl +resource "grafana_data_source" "azure_monitor" { + type = "grafana-azure-monitor-datasource" + name = "Azure Monitor" + + json_data_encoded = jsonencode({ + azureAuthType = "clientsecret" + cloudName = "azuremonitor" + tenantId = "" + clientId = "" + subscriptionId = "" + enableBasicLogs = true + }) + + secure_json_data_encoded = jsonencode({ + clientSecret = "" + }) +} +``` + +For more information about the Grafana Terraform provider, refer to the [provider documentation](https://registry.terraform.io/providers/grafana/grafana/latest/docs) and the [grafana_data_source resource](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/data_source). diff --git a/docs/sources/datasources/azure-monitor/query-editor/index.md b/docs/sources/datasources/azure-monitor/query-editor/index.md index 6415be1281c..a8c763d9280 100644 --- a/docs/sources/datasources/azure-monitor/query-editor/index.md +++ b/docs/sources/datasources/azure-monitor/query-editor/index.md @@ -21,6 +21,7 @@ labels: menuTitle: Query editor title: Azure Monitor query editor weight: 300 +last_reviewed: 2025-12-04 refs: query-transform-data-query-options: - pattern: /docs/grafana/ @@ -32,30 +33,85 @@ refs: destination: /docs/grafana//panels-visualizations/query-transform-data/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana//panels-visualizations/query-transform-data/ + configure-azure-monitor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/configure/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/configure/ + explore: + - pattern: /docs/grafana/ + destination: /docs/grafana//explore/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//explore/ + troubleshoot-azure-monitor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/troubleshooting/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/troubleshooting/ + configure-grafana-feature-toggles: + - pattern: /docs/grafana/ + destination: /docs/grafana//setup-grafana/configure-grafana/feature-toggles/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//setup-grafana/configure-grafana/feature-toggles/ + template-variables: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/template-variables/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/template-variables/ + alerting-azure-monitor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/alerting/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/alerting/ + annotations-azure-monitor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/annotations/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/annotations/ --- # Azure Monitor query editor -This topic explains querying specific to the Azure Monitor data source. -For general documentation on querying data sources in Grafana, see [Query and transform data](ref:query-transform-data). +Grafana provides a query editor for the Azure Monitor data source, which is located on the [Explore page](ref:explore). You can also access the Azure Monitor query editor from a dashboard panel. Click the menu in the upper right of the panel and select **Edit**. -## Choose a query editing mode +This document explains querying specific to the Azure Monitor data source. +For general documentation on querying data sources in Grafana, refer to [Query and transform data](ref:query-transform-data). -The Azure Monitor data source's query editor has three modes depending on which Azure service you want to query: +The Azure Monitor data source can query data from Azure Monitor Metrics and Logs, the Azure Resource Graph, and Application Insights Traces. Each source has its own specialized query editor. + +## Before you begin + +- Ensure you have [configured the Azure Monitor data source](ref:configure-azure-monitor). +- Verify your credentials have appropriate permissions for the resources you want to query. + +## Key concepts + +If you're new to Azure Monitor, here are some key terms used throughout this documentation: + +| Term | Description | +| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **KQL (Kusto Query Language)** | The query language used for Azure Monitor Logs and Azure Resource Graph. KQL uses a pipe-based syntax similar to Unix commands and is optimized for read-only data exploration. If you know SQL, the [SQL to Kusto cheat sheet](https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/sqlcheatsheet) can help you get started. | +| **Log Analytics workspace** | An Azure resource that collects and stores log data from your Azure resources, applications, and services. You query this data using KQL. | +| **Application Insights** | Azure's application performance monitoring (APM) service. It collects telemetry data like requests, exceptions, and traces from your applications. | +| **Metrics vs. Logs** | **Metrics** are lightweight numeric values collected at regular intervals (e.g., CPU percentage). **Logs** are detailed records of events with varying schemas (e.g., request logs, error messages). Metrics use a visual query builder; Logs require KQL. | + +## Choose a query editor mode + +The Azure Monitor data source's query editor has four modes depending on which Azure service you want to query: - **Metrics** for [Azure Monitor Metrics](#query-azure-monitor-metrics) - **Logs** for [Azure Monitor Logs](#query-azure-monitor-logs) -- [**Azure Resource Graph**](#query-azure-resource-graph) - **Traces** for [Application Insights Traces](#query-application-insights-traces) +- **Azure Resource Graph** for [Azure Resource Graph](#query-azure-resource-graph) ## Query Azure Monitor Metrics -Azure Monitor Metrics collects numeric data from [supported resources](https://docs.microsoft.com/en-us/azure/azure-monitor/monitor-reference), and you can query them to investigate your resources' health and usage and maximise availability and performance. +Azure Monitor Metrics collects numeric data from [supported resources](https://docs.microsoft.com/en-us/azure/azure-monitor/monitor-reference), and you can query them to investigate your resources' health and usage and maximize availability and performance. Monitor Metrics use a lightweight format that stores only numeric data in a specific structure and supports near real-time scenarios, making it useful for fast detection of issues. In contrast, Azure Monitor Logs can store a variety of data types, each with their own structure. -{{< figure src="/static/img/docs/azure-monitor/query-editor-metrics.png" max-width="800px" class="docs-image--no-shadow" caption="Azure Logs Metrics sample query visualizing CPU percentage over time" >}} +{{< figure src="/static/img/docs/azure-monitor/query-editor-metrics.png" max-width="800px" class="docs-image--no-shadow" caption="Azure Monitor Metrics sample query visualizing CPU percentage over time" >}} ### Create a Metrics query @@ -85,7 +141,7 @@ Optionally, you can apply further aggregations or filter by dimensions. The available options change depending on what is relevant to the selected metric. -You can also augment queries by using [template variables](../template-variables/). +You can also augment queries by using [template variables](ref:template-variables). ### Format legend aliases @@ -109,7 +165,7 @@ For example: | `{{ dimensionname }}` | _(Legacy for backward compatibility)_ Replaced with the name of the first dimension. | | `{{ dimensionvalue }}` | _(Legacy for backward compatibility)_ Replaced with the value of the first dimension. | -### Filter using dimensions +### Filter with dimensions Some metrics also have dimensions, which associate additional metadata. Dimensions are represented as key-value pairs assigned to each value of a metric. @@ -121,7 +177,7 @@ For more information on multi-dimensional metrics, refer to the [Azure Monitor d ## Query Azure Monitor Logs -Azure Monitor Logs collects and organises log and performance data from [supported resources](https://docs.microsoft.com/en-us/azure/azure-monitor/monitor-reference), and makes many sources of data available to query together with the [Kusto Query Language (KQL)](https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/). +Azure Monitor Logs collects and organizes log and performance data from [supported resources](https://docs.microsoft.com/en-us/azure/azure-monitor/monitor-reference), and makes many sources of data available to query together with the [Kusto Query Language (KQL)](https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/). While Azure Monitor Metrics stores only simplified numerical data, Logs can store different data types, each with their own structure. You can also perform complex analysis of Logs data by using KQL. @@ -130,6 +186,32 @@ The Azure Monitor data source also supports querying of [Basic Logs](https://lea {{< figure src="/static/img/docs/azure-monitor/query-editor-logs.png" max-width="800px" class="docs-image--no-shadow" caption="Azure Monitor Logs sample query comparing successful requests to failed requests" >}} +### Logs query builder (public preview) + +{{< admonition type="note" >}} +The Logs query builder is a [public preview feature](/docs/release-life-cycle/). It may not be enabled in all Grafana environments. +{{< /admonition >}} + +The Logs query builder provides a visual interface for building Azure Monitor Logs queries without writing KQL. This is helpful if you're new to KQL or want to quickly build simple queries. + +**To enable the Logs query builder:** + +1. Enable the `azureMonitorLogsBuilderEditor` [feature toggle](ref:configure-grafana-feature-toggles) in your Grafana configuration. +1. Restart Grafana for the change to take effect. + +**To switch between Builder and Code modes:** + +When the feature is enabled, a **Builder / Code** toggle appears in the Logs query editor: + +- **Builder**: Use the visual interface to select tables, columns, filters, and aggregations. The builder generates the KQL query for you. +- **Code**: Write KQL queries directly. Use this mode for complex queries that require full KQL capabilities. + +New queries default to Builder mode. Existing queries that were created with raw KQL remain in Code mode. + +{{< admonition type="note" >}} +You can switch from Builder to Code mode at any time to view or edit the generated KQL. However, switching from Code to Builder mode may not preserve complex queries that can't be represented in the builder interface. +{{< /admonition >}} + ### Create a Logs query **To create a Logs query:** @@ -140,13 +222,13 @@ The Azure Monitor data source also supports querying of [Basic Logs](https://lea Alternatively, you can dynamically query all resources under a single resource group or subscription. {{< admonition type="note" >}} - If a timespan is specified in the query, the overlap of the timespan between the query and the dashboard will be used as the query timespan. See the [API documentation for + If a time span is specified in the query, the overlap between the query time span and the dashboard time range will be used. See the [API documentation for details.](https://learn.microsoft.com/en-us/rest/api/loganalytics/dataaccess/query/get?tabs=HTTP#uri-parameters) {{< /admonition >}} 1. Enter your KQL query. -You can also augment queries by using [template variables](../template-variables/). +You can also augment queries by using [template variables](ref:template-variables). **To create a Basic Logs query:** @@ -161,7 +243,7 @@ You can also augment queries by using [template variables](../template-variables {{< /admonition >}} 1. Enter your KQL query. -You can also augment queries by using [template variables](https://grafana.com/docs/grafana//datasources/azure-monitor/template-variables/). +You can also augment queries by using [template variables](ref:template-variables). ### Logs query examples @@ -174,24 +256,28 @@ The Azure documentation includes resources to help you learn KQL: - [Tutorial: Use Kusto queries in Azure Monitor](https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/tutorial?pivots=azuremonitor) - [SQL to Kusto cheat sheet](https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/sqlcheatsheet) -> **Time-range:** The time-range that will be used for the query can be modified via the time-range switch. Selecting `Query` will only make use of time-ranges specified within the query. -> Specifying `Dashboard` will only make use of the Grafana time-range. -> If there are no time-ranges specified within the query, the default Log Analytics time-range will apply. -> For more details on this change, refer to the [Azure Monitor Logs API documentation](https://learn.microsoft.com/en-us/rest/api/loganalytics/dataaccess/query/get?tabs=HTTP#uri-parameters). -> If the `Intersection` option was previously chosen it will be migrated by default to `Dashboard`. +{{< admonition type="note" >}} +**Time-range:** The time-range used for the query can be modified via the time-range switch: -This example query returns a virtual machine's CPU performance, averaged over 5ms time grains: +- Selecting **Query** uses only time-ranges specified within the query. +- Selecting **Dashboard** uses only the Grafana dashboard time-range. +- If no time-range is specified in the query, the default Log Analytics time-range applies. + +For more details, refer to the [Azure Monitor Logs API documentation](https://learn.microsoft.com/en-us/rest/api/loganalytics/dataaccess/query/get?tabs=HTTP#uri-parameters). If you previously used the `Intersection` option, it has been migrated to `Dashboard`. +{{< /admonition >}} + +This example query returns a virtual machine's CPU performance, averaged over 5-minute time grains: ```kusto Perf -# $__timeFilter is a special Grafana macro that filters the results to the time span of the dashboard +// $__timeFilter is a special Grafana macro that filters the results to the time span of the dashboard | where $__timeFilter(TimeGenerated) | where CounterName == "% Processor Time" | summarize avg(CounterValue) by bin(TimeGenerated, 5m), Computer | order by TimeGenerated asc ``` -Use time series queries for values that change over time, usually for graph visualisations such as the Time series panel. +Use time series queries for values that change over time, usually for graph visualizations such as the Time series panel. Each query should return at least a datetime column and numeric value column. The result must also be sorted in ascending order by the datetime column. @@ -357,21 +443,33 @@ Application Insights stores trace data in an underlying Log Analytics workspace This query type only supports Application Insights resources. {{< /admonition >}} -Running a query of this kind will return all trace data within the timespan specified by the panel/dashboard. +1. (Optional) Specify an **Operation ID** value to filter traces. +1. (Optional) Specify **event types** to filter by. +1. (Optional) Specify **event properties** to filter by. +1. (Optional) Change the **Result format** to switch between tabular format and trace format. -Optionally, you can apply further filtering or select a specific Operation ID to query. The result format can also be switched between a tabular format or the trace format which will return the data in a format that can be used with the Trace visualization. + {{< admonition type="note" >}} + Selecting the trace format filters events to only the `trace` type. Use this format with the Trace visualization. + {{< /admonition >}} -{{< admonition type="note" >}} -Selecting the trace format will filter events with the `trace` type. -{{< /admonition >}} +Running a query returns all trace data within the time span specified by the panel or dashboard time range. -1. Specify an Operation ID value. -1. Specify event types to filter by. -1. Specify event properties to filter by. +You can also augment queries by using [template variables](ref:template-variables). -You can also augment queries by using [template variables](../template-variables/). +## Use queries for alerting and recording rules -## Working with large Azure resource data sets +All Azure Monitor query types (Metrics, Logs, Azure Resource Graph, and Traces) can be used with Grafana Alerting and recording rules. + +For detailed information about creating alert rules, supported query types, authentication requirements, and examples, refer to [Azure Monitor alerting](ref:alerting-azure-monitor). + +## Work with large Azure resource datasets If a request exceeds the [maximum allowed value of records](https://docs.microsoft.com/en-us/azure/governance/resource-graph/concepts/work-with-data#paging-results), the result is paginated and only the first page of results are returned. You can use filters to reduce the amount of records returned under that value. + +## Next steps + +- [Use template variables](../template-variables/) to create dynamic, reusable dashboards +- [Add annotations](ref:annotations-azure-monitor) to overlay events on your graphs +- [Set up alerting](ref:alerting-azure-monitor) to create alert rules based on Azure Monitor data +- [Troubleshoot](ref:troubleshoot-azure-monitor) common query and configuration issues diff --git a/docs/sources/datasources/azure-monitor/template-variables/index.md b/docs/sources/datasources/azure-monitor/template-variables/index.md index 1db472a4251..3cedadef9b5 100644 --- a/docs/sources/datasources/azure-monitor/template-variables/index.md +++ b/docs/sources/datasources/azure-monitor/template-variables/index.md @@ -23,6 +23,7 @@ labels: menuTitle: Template variables title: Azure Monitor template variables weight: 400 +last_reviewed: 2025-12-04 refs: variables: - pattern: /docs/grafana/ @@ -34,6 +35,11 @@ refs: destination: /docs/grafana//dashboards/variables/add-template-variables/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana//dashboards/variables/add-template-variables/ + configure-azure-monitor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/configure/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/configure/ --- # Azure Monitor template variables @@ -42,58 +48,173 @@ Instead of hard-coding details such as resource group or resource name values in This helps you create more interactive, dynamic, and reusable dashboards. Grafana refers to such variables as template variables. -For an introduction to templating and template variables, refer to the [Templating](ref:variables) and [Add and manage variables](ref:add-template-variables) documentation. +For an introduction to templating and template variables, refer to the [Templating](ref:variables) and [Add and manage variables](ref:add-template-variables). -## Use query variables +## Before you begin -You can specify these Azure Monitor data source queries in the Variable edit view's **Query Type** field. +- Ensure you have [configured the Azure Monitor data source](ref:configure-azure-monitor). +- If you want template variables to auto-populate subscriptions, set a **Default Subscription** in the data source configuration. -| Name | Description | -| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| **Subscriptions** | Returns subscriptions. | -| **Resource Groups** | Returns resource groups for a specified subscription. Supports multi-value. | -| **Namespaces** | Returns metric namespaces for the specified subscription. If a resource group is provided, only the namespaces within that group are returned. | -| **Regions** | Returns regions for the specified subscription | -| **Resource Names** | Returns a list of resource names for a specified subscription, resource group and namespace. Supports multi-value. | -| **Metric Names** | Returns a list of metric names for a resource. | -| **Workspaces** | Returns a list of workspaces for the specified subscription. | -| **Logs** | Use a KQL query to return values. | -| **Custom Namespaces** | Returns metric namespaces for the specified resource. | -| **Custom Metric Names** | Returns a list of custom metric names for the specified resource. | +## Create a template variable + +To create a template variable for Azure Monitor: + +1. Open the dashboard where you want to add the variable. +1. Click **Dashboard settings** (gear icon) in the top navigation. +1. Select **Variables** in the left menu. +1. Click **Add variable**. +1. Enter a **Name** for your variable (e.g., `subscription`, `resourceGroup`, `resource`). +1. In the **Type** dropdown, select **Query**. +1. In the **Data source** dropdown, select your Azure Monitor data source. +1. In the **Query Type** dropdown, select the appropriate query type (see [Available query types](#available-query-types)). +1. Configure any additional fields required by the selected query type. +1. Click **Run query** to preview the variable values. +1. Configure display options such as **Multi-value** or **Include All option** as needed. +1. Click **Apply** to save the variable. + +## Available query types + +The Azure Monitor data source provides the following query types for template variables: + +| Query type | Description | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| **Subscriptions** | Returns a list of Azure subscriptions accessible to the configured credentials. | +| **Resource Groups** | Returns resource groups for a specified subscription. Supports multi-value selection. | +| **Namespaces** | Returns metric namespaces for the specified subscription. If a resource group is specified, returns only namespaces within that group. | +| **Regions** | Returns Azure regions available for the specified subscription. | +| **Resource Names** | Returns resource names for a specified subscription, resource group, and namespace. Supports multi-value selection. | +| **Metric Names** | Returns available metric names for a specified resource. | +| **Workspaces** | Returns Log Analytics workspaces for the specified subscription. | +| **Logs** | Executes a KQL query and returns the results as variable values. See [Create a Logs variable](#create-a-logs-variable). | +| **Custom Namespaces** | Returns custom metric namespaces for a specified resource. | +| **Custom Metric Names** | Returns custom metric names for a specified resource. | {{< admonition type="note" >}} -Custom metrics cannot be emitted against a subscription or resource group. Select resources only when you need to retrieve custom metric namespaces or custom metric names associated with a specific resource. +Custom metrics cannot be emitted against a subscription or resource group. Select specific resources when retrieving custom metric namespaces or custom metric names. {{< /admonition >}} -You can use any Log Analytics Kusto Query Language (KQL) query that returns a single list of values in the `Query` field. -For example: +## Create cascading variables -| Query | List of values returned | -| ----------------------------------------------------------------------------------------- | --------------------------------------- | -| `workspace("myWorkspace").Heartbeat \| distinct Computer` | Virtual machines | -| `workspace("$workspace").Heartbeat \| distinct Computer` | Virtual machines with template variable | -| `workspace("$workspace").Perf \| distinct ObjectName` | Objects from the Perf table | -| `workspace("$workspace").Perf \| where ObjectName == "$object"` `\| distinct CounterName` | Metric names from the Perf table | +Cascading variables (also called dependent or chained variables) allow you to create dropdown menus that filter based on previous selections. This is useful for drilling down from subscription to resource group to specific resource. -### Query variable example +### Example: Subscription → Resource Group → Resource Name -This time series query uses query variables: +**Step 1: Create a Subscription variable** + +1. Create a variable named `subscription`. +1. Set **Query Type** to **Subscriptions**. + +**Step 2: Create a Resource Group variable** + +1. Create a variable named `resourceGroup`. +1. Set **Query Type** to **Resource Groups**. +1. In the **Subscription** field, select `$subscription`. + +**Step 3: Create a Resource Name variable** + +1. Create a variable named `resource`. +1. Set **Query Type** to **Resource Names**. +1. In the **Subscription** field, select `$subscription`. +1. In the **Resource Group** field, select `$resourceGroup`. +1. Select the appropriate **Namespace** for your resources (e.g., `Microsoft.Compute/virtualMachines`). + +Now when you change the subscription, the resource group dropdown updates automatically, and when you change the resource group, the resource name dropdown updates. + +## Create a Logs variable + +The **Logs** query type lets you use a KQL query to populate variable values. The query must return a single column of values. + +**To create a Logs variable:** + +1. Create a new variable with **Query Type** set to **Logs**. +1. Select a **Resource** (Log Analytics workspace or Application Insights resource). +1. Enter a KQL query that returns a single column. + +### Logs variable query examples + +| Query | Returns | +| ----------------------------------------- | ------------------------------------- | +| `Heartbeat \| distinct Computer` | List of virtual machine names | +| `Perf \| distinct ObjectName` | List of performance object names | +| `AzureActivity \| distinct ResourceGroup` | List of resource groups with activity | +| `AppRequests \| distinct Name` | List of application request names | + +You can reference other variables in your Logs query: + +```kusto +workspace("$workspace").Heartbeat | distinct Computer +``` + +```kusto +workspace("$workspace").Perf +| where ObjectName == "$object" +| distinct CounterName +``` + +## Variable refresh options + +Control when your variables refresh by setting the **Refresh** option: + +| Option | Behavior | +| ------------------------ | ----------------------------------------------------------------------------------------- | +| **On dashboard load** | Variables refresh each time the dashboard loads. Best for data that changes infrequently. | +| **On time range change** | Variables refresh when the dashboard time range changes. Use for time-sensitive queries. | + +For dashboards with many variables or complex queries, use **On dashboard load** to improve performance. + +## Use variables in queries + +After you create template variables, you can use them in your Azure Monitor queries by referencing them with the `$` prefix. + +### Metrics query example + +In a Metrics query, select your variables in the resource picker fields: + +- **Subscription**: `$subscription` +- **Resource Group**: `$resourceGroup` +- **Resource Name**: `$resource` + +### Logs query example + +Reference variables directly in your KQL queries: ```kusto Perf | where ObjectName == "$object" and CounterName == "$metric" | where TimeGenerated >= $__timeFrom() and TimeGenerated <= $__timeTo() -| where $__contains(Computer, $computer) +| where $__contains(Computer, $computer) | summarize avg(CounterValue) by bin(TimeGenerated, $__interval), Computer | order by TimeGenerated asc ``` -### Multi-value variables +## Multi-value variables -It is possible to select multiple values for **Resource Groups** and **Resource Names** and use a single metrics query pointing to those values as long as they: +You can enable **Multi-value** selection for **Resource Groups** and **Resource Names** variables. When using multi-value variables in a Metrics query, all selected resources must: -- Belong to the same subscription. -- Are in the same region. -- Are of the same type (namespace). +- Belong to the same subscription +- Be in the same Azure region +- Be of the same resource type (namespace) -Also, note that if a template variable pointing to multiple resource groups or names is used in another template variable as a parameter (e.g. to retrieve metric names), only the first value will be used. This means that the combination of the first resource group and name selected should be valid. +{{< admonition type="note" >}} +When a multi-value variable is used as a parameter in another variable query (for example, to retrieve metric names), only the first selected value is used. Ensure the first resource group and resource name combination is valid. +{{< /admonition >}} + +## Troubleshoot template variables + +If you encounter issues with template variables, try the following solutions. + +### Variable returns no values + +- Verify the Azure Monitor data source is configured correctly and can connect to Azure. +- Check that the credentials have appropriate permissions to list the requested resources. +- For cascading variables, ensure parent variables have valid selections. + +### Variable values are outdated + +- Check the **Refresh** setting and adjust if needed. +- Click the refresh icon next to the variable dropdown to manually refresh. + +### Multi-value selection not working in queries + +- Ensure the resources meet the requirements (same subscription, region, and type). +- For Logs queries, use the `$__contains()` macro to handle multi-value variables properly. diff --git a/docs/sources/datasources/azure-monitor/troubleshooting/index.md b/docs/sources/datasources/azure-monitor/troubleshooting/index.md new file mode 100644 index 00000000000..b2d5a9efc32 --- /dev/null +++ b/docs/sources/datasources/azure-monitor/troubleshooting/index.md @@ -0,0 +1,320 @@ +--- +aliases: + - ../../data-sources/azure-monitor/troubleshooting/ +description: Troubleshooting guide for the Azure Monitor data source in Grafana +keywords: + - grafana + - azure + - monitor + - troubleshooting + - errors + - authentication + - query +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Troubleshoot +title: Troubleshoot Azure Monitor data source issues +weight: 500 +last_reviewed: 2025-12-04 +refs: + configure-azure-monitor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/configure/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/configure/ + template-variables: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/template-variables/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/template-variables/ + query-editor: + - pattern: /docs/grafana/ + destination: /docs/grafana//datasources/azure-monitor/query-editor/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//datasources/azure-monitor/query-editor/ +--- + +# Troubleshoot Azure Monitor data source issues + +This document provides solutions to common issues you may encounter when configuring or using the Azure Monitor data source. + +## Configuration and authentication errors + +These errors typically occur when setting up the data source or when authentication credentials are invalid. + +### "Authorization failed" or "Access denied" + +**Symptoms:** + +- Save & test fails with "Authorization failed" +- Queries return "Access denied" errors +- Subscriptions don't load when clicking **Load Subscriptions** + +**Possible causes and solutions:** + +| Cause | Solution | +| -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| App registration doesn't have required permissions | Assign the `Reader` role to the app registration on the subscription or resource group you want to monitor. Refer to the [Azure documentation for role assignments](https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal?tabs=current). | +| Incorrect tenant ID, client ID, or client secret | Verify the credentials in the Azure Portal under **App registrations** > your app > **Overview** (for IDs) and **Certificates & secrets** (for secret). | +| Client secret has expired | Create a new client secret in Azure and update the data source configuration. | +| Managed Identity not enabled on the Azure resource | For VMs, enable managed identity in the Azure Portal under **Identity**. For App Service, enable it under **Identity** in the app settings. | +| Managed Identity not assigned the Reader role | Assign the `Reader` role to the managed identity on the target subscription or resources. | + +### "Invalid client secret" or "Client secret not found" + +**Symptoms:** + +- Authentication fails immediately after configuration +- Error message references invalid credentials + +**Solutions:** + +1. Ensure you copied the client secret **value**, not the secret ID. In Azure Portal under **Certificates & secrets**, the secret value is only shown once when created. The secret ID is a different identifier and won't work for authentication. +2. Verify the client secret was copied correctly (no extra spaces or truncation). +3. Check if the secret has expired in Azure Portal under **App registrations** > your app > **Certificates & secrets**. +4. Create a new secret and update the data source configuration. + +### "Tenant not found" or "Invalid tenant ID" + +**Symptoms:** + +- Data source test fails with tenant-related errors +- Unable to authenticate + +**Solutions:** + +1. Verify the Directory (tenant) ID in Azure Portal under **Microsoft Entra ID** > **Overview**. +2. Ensure you're using the correct Azure cloud setting (Azure, Azure Government, or Azure China). +3. Check that the tenant ID is a valid GUID format. + +### Managed Identity not working + +**Symptoms:** + +- Managed Identity option is available but authentication fails +- Error: "Managed identity authentication is not available" + +**Solutions:** + +1. Verify `managed_identity_enabled = true` is set in the Grafana server configuration under `[azure]`. +2. Confirm the Azure resource hosting Grafana has managed identity enabled. +3. For user-assigned managed identity, ensure `managed_identity_client_id` is set correctly. +4. Verify the managed identity has the `Reader` role on the target resources. +5. Restart Grafana after changing server configuration. + +### Workload Identity not working + +**Symptoms:** + +- Workload Identity authentication fails in Kubernetes/AKS environment +- Token file errors + +**Solutions:** + +1. Verify `workload_identity_enabled = true` is set in the Grafana server configuration. +2. Check that the service account is correctly annotated for workload identity. +3. Verify the federated credential is configured in Azure. +4. Ensure the token path is accessible to the Grafana pod. +5. Check the workload identity webhook is running in the cluster. + +## Query errors + +These errors occur when executing queries against Azure Monitor services. + +### "No data" or empty results + +**Symptoms:** + +- Query executes without error but returns no data +- Charts show "No data" message + +**Possible causes and solutions:** + +| Cause | Solution | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| Time range doesn't contain data | Expand the dashboard time range or verify data exists in Azure Portal. | +| Wrong resource selected | Verify you've selected the correct subscription, resource group, and resource. | +| Metric not available for resource | Not all metrics are available for all resources. Check available metrics in Azure Portal under the resource's **Metrics** blade. | +| Metric has no values | Some metrics only populate under certain conditions (e.g., error counts when errors occur). | +| Permissions issue | Verify the identity has read access to the specific resource. | + +### "Bad request" or "Invalid query" + +**Symptoms:** + +- Query fails with 400 error +- Error message indicates query syntax issues + +**Solutions for Logs queries:** + +1. Validate your KQL syntax in the Azure Portal Log Analytics query editor. +2. Check for typos in table names or column names. +3. Ensure referenced tables exist in the selected workspace. +4. Verify the time range is valid (not in the future, not too far in the past for data retention). + +**Solutions for Metrics queries:** + +1. Verify the metric name is valid for the selected resource type. +2. Check that dimension filters use valid dimension names and values. +3. Ensure the aggregation type is supported for the selected metric. + +### "Resource not found" + +**Symptoms:** + +- Query fails with 404 error +- Resource picker shows resources that can't be queried + +**Solutions:** + +1. Verify the resource still exists in Azure (it may have been deleted or moved). +2. Check that the subscription is correct. +3. Refresh the resource picker by re-selecting the subscription. +4. Verify the identity has access to the resource's resource group. + +### Logs query timeout + +**Symptoms:** + +- Query runs for a long time then fails +- Error mentions timeout or query limits + +**Solutions:** + +1. Narrow the time range to reduce data volume. +2. Add filters to reduce the result set. +3. Use `summarize` to aggregate data instead of returning raw rows. +4. Consider using Basic Logs for large datasets (if enabled). +5. Break complex queries into smaller parts. + +### "Metrics not available" for a resource + +**Symptoms:** + +- Resource appears in picker but no metrics are listed +- Metric dropdown is empty + +**Solutions:** + +1. Verify the resource type supports Azure Monitor metrics. +2. Check if the resource is in a region that supports metrics. +3. Some resources require diagnostic settings to emit metrics—configure these in Azure Portal. +4. Try selecting a different namespace for the resource. + +## Azure Resource Graph errors + +These errors are specific to Azure Resource Graph (ARG) queries. + +### "Query execution failed" + +**Symptoms:** + +- ARG query fails with execution errors +- Results don't match expected resources + +**Solutions:** + +1. Validate query syntax in Azure Portal Resource Graph Explorer. +2. Check that you have access to the subscriptions being queried. +3. Verify table names are correct (e.g., `Resources`, `ResourceContainers`). +4. Some ARG features require specific permissions, check [ARG documentation](https://docs.microsoft.com/en-us/azure/governance/resource-graph/). + +### Query returns incomplete results + +**Symptoms:** + +- Not all expected resources appear in results +- Results seem truncated + +**Solutions:** + +1. ARG queries are paginated. The data source handles pagination automatically, but very large result sets may be limited. +2. Add filters to reduce result set size. +3. Verify you have access to all subscriptions containing the resources. + +## Application Insights Traces errors + +These errors are specific to the Traces query type. + +### "No traces found" + +**Symptoms:** + +- Trace query returns empty results +- Operation ID search finds nothing + +**Solutions:** + +1. Verify the Application Insights resource is collecting trace data. +2. Check that the time range includes when the traces were generated. +3. Ensure the Operation ID is correct (copy directly from another trace or log). +4. Verify the identity has access to the Application Insights resource. + +## Template variable errors + +For detailed troubleshooting of template variables, refer to the [template variables troubleshooting section](ref:template-variables). + +### Variables return no values + +**Solutions:** + +1. Verify the data source connection is working (test it in the data source settings). +2. Check that parent variables (for cascading variables) have valid selections. +3. Verify the identity has permissions to list the requested resources. +4. For Logs variables, ensure the KQL query returns a single column. + +### Variables are slow to load + +**Solutions:** + +1. Set variable refresh to **On dashboard load** instead of **On time range change**. +2. Reduce the scope of variable queries (e.g., filter by resource group instead of entire subscription). +3. For Logs variables, optimize the KQL query to return results faster. + +## Connection and network errors + +These errors indicate problems with network connectivity between Grafana and Azure services. + +### "Connection refused" or timeout errors + +**Symptoms:** + +- Data source test fails with network errors +- Queries timeout without returning results + +**Solutions:** + +1. Verify network connectivity from Grafana to Azure endpoints. +2. Check firewall rules allow outbound HTTPS (port 443) to Azure services. +3. For private networks, ensure Private Link or VPN is configured correctly. +4. For Grafana Cloud, configure [Private Data Source Connect](ref:configure-azure-monitor) if accessing private resources. + +### SSL/TLS certificate errors + +**Symptoms:** + +- Certificate validation failures +- SSL handshake errors + +**Solutions:** + +1. Ensure the system time is correct (certificate validation fails with incorrect time). +2. Verify corporate proxy isn't intercepting HTTPS traffic. +3. Check that required CA certificates are installed on the Grafana server. + +## Get additional help + +If you've tried the solutions above and still encounter issues: + +1. Check the [Grafana community forums](https://community.grafana.com/) for similar issues. +1. Review the [Azure Monitor data source GitHub issues](https://github.com/grafana/grafana/issues) for known bugs. +1. Enable debug logging in Grafana to capture detailed error information. +1. Contact Grafana Support if you're an Enterprise, Cloud Pro or Cloud Contracted user. +1. When reporting issues, include: + - Grafana version + - Error messages (redact sensitive information) + - Steps to reproduce + - Relevant configuration (redact credentials) From cfc5d96c34c08b66b5c381dc6de6820a8a82d7be Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Fri, 12 Dec 2025 14:39:43 -0700 Subject: [PATCH 087/297] Dashboard Schema V2: Fix panel query tab (#115276) fix panel query tab for v2 schema --- .../PanelDataQueriesTab.test.tsx | 102 +++++++- .../PanelDataPane/PanelDataQueriesTab.tsx | 11 + .../panel-edit/testfiles/testDashboard.ts | 223 ++++++++++++++++++ 3 files changed, 335 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx index 40da4a158a8..27b924b5936 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx @@ -25,10 +25,17 @@ import { DashboardDataDTO } from 'app/types/dashboard'; import { PanelInspectDrawer } from '../../inspect/PanelInspectDrawer'; import { PanelTimeRange, PanelTimeRangeState } from '../../scene/panel-timerange/PanelTimeRange'; +import { DashboardLayoutManager } from '../../scene/types/DashboardLayoutManager'; +import { transformSaveModelSchemaV2ToScene } from '../../serialization/transformSaveModelSchemaV2ToScene'; import { transformSaveModelToScene } from '../../serialization/transformSaveModelToScene'; import { findVizPanelByKey } from '../../utils/utils'; import { buildPanelEditScene } from '../PanelEditor'; -import { testDashboard, panelWithTransformations, panelWithQueriesOnly } from '../testfiles/testDashboard'; +import { + testDashboard, + panelWithTransformations, + panelWithQueriesOnly, + testDashboardV2, +} from '../testfiles/testDashboard'; import { PanelDataQueriesTab, PanelDataQueriesTabRendered } from './PanelDataQueriesTab'; @@ -824,6 +831,78 @@ describe('PanelDataQueriesTab', () => { expect(queriesTab.state.dsSettings?.uid).toBe('gdev-testdata'); }); }); + + describe('V2 schema behavior - panel datasource undefined but queries have datasource', () => { + it('should load datasource from first query for V2 panel with prometheus datasource', async () => { + // panel-1 has a query with prometheus datasource + const { queriesTab } = await setupV2Scene('panel-1'); + + // V2 panels have undefined panel-level datasource for non-mixed panels + expect(queriesTab.queryRunner.state.datasource).toBeUndefined(); + + // But the query has its own datasource + expect(queriesTab.queryRunner.state.queries[0].datasource).toEqual({ + type: 'grafana-prometheus-datasource', + uid: 'gdev-prometheus', + }); + + // Should load the datasource from the first query + expect(queriesTab.state.datasource?.uid).toBe('gdev-prometheus'); + expect(queriesTab.state.dsSettings?.uid).toBe('gdev-prometheus'); + }); + + it('should load datasource from first query for V2 panel with testdata datasource', async () => { + // panel-2 has a query with testdata datasource + const { queriesTab } = await setupV2Scene('panel-2'); + + // V2 panels have undefined panel-level datasource for non-mixed panels + expect(queriesTab.queryRunner.state.datasource).toBeUndefined(); + + // But the query has its own datasource + expect(queriesTab.queryRunner.state.queries[0].datasource).toEqual({ + type: 'grafana-testdata-datasource', + uid: 'gdev-testdata', + }); + + // Should load the datasource from the first query + expect(queriesTab.state.datasource?.uid).toBe('gdev-testdata'); + expect(queriesTab.state.dsSettings?.uid).toBe('gdev-testdata'); + }); + + it('should fall back to last used datasource when V2 query has no explicit datasource', async () => { + store.exists.mockReturnValue(true); + store.getObject.mockImplementation((key: string, def: unknown) => { + if (key === PANEL_EDIT_LAST_USED_DATASOURCE) { + return { + dashboardUid: 'v2-dashboard-uid', + datasourceUid: 'gdev-testdata', + }; + } + return def; + }); + + // panel-3 has a query with NO explicit datasource (datasource.name is undefined) + const { queriesTab } = await setupV2Scene('panel-3'); + + // V2 panel with no explicit datasource on query should fall back to last used + expect(queriesTab.state.datasource?.uid).toBe('gdev-testdata'); + expect(queriesTab.state.dsSettings?.uid).toBe('gdev-testdata'); + }); + + it('should use panel-level datasource when available (V1 behavior preserved)', async () => { + const { queriesTab } = await setupScene('panel-1'); + + // V1 panels have panel-level datasource set + expect(queriesTab.queryRunner.state.datasource).toEqual({ + uid: 'gdev-testdata', + type: 'grafana-testdata-datasource', + }); + + // Should use the panel-level datasource + expect(queriesTab.state.datasource?.uid).toBe('gdev-testdata'); + expect(queriesTab.state.dsSettings?.uid).toBe('gdev-testdata'); + }); + }); }); }); @@ -844,3 +923,24 @@ async function setupScene(panelId: string) { return { panel, scene: dashboard, queriesTab }; } + +// Setup V2 scene - uses transformSaveModelSchemaV2ToScene +async function setupV2Scene(panelKey: string) { + const dashboard = transformSaveModelSchemaV2ToScene(testDashboardV2); + + const vizPanels = (dashboard.state.body as DashboardLayoutManager).getVizPanels(); + const panel = vizPanels.find((p) => p.state.key === panelKey)!; + + const panelEditor = buildPanelEditScene(panel); + dashboard.setState({ editPanel: panelEditor }); + + deactivators.push(dashboard.activate()); + deactivators.push(panelEditor.activate()); + + const queriesTab = panelEditor.state.dataPane!.state.tabs[0] as PanelDataQueriesTab; + deactivators.push(queriesTab.activate()); + + await Promise.resolve(); + + return { panel, scene: dashboard, queriesTab }; +} diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx index d70fbf9c9c4..8bfa97461d3 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.tsx @@ -86,6 +86,17 @@ export class PanelDataQueriesTab extends SceneObjectBase = { + kind: 'DashboardWithAccessInfo', + metadata: { + name: 'v2-dashboard-uid', + namespace: 'default', + labels: {}, + generation: 1, + resourceVersion: '1', + creationTimestamp: new Date().toISOString(), + }, + spec: { + title: 'V2 Test Dashboard', + description: 'Test dashboard for V2 schema', + tags: [], + cursorSync: 'Off', + liveNow: false, + editable: true, + preload: false, + links: [], + variables: [], + annotations: [], + timeSettings: { + from: 'now-6h', + to: 'now', + autoRefresh: '', + autoRefreshIntervals: ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d'], + fiscalYearStartMonth: 0, + hideTimepicker: false, + timezone: '', + weekStart: undefined, + quickRanges: [], + }, + elements: { + 'panel-1': { + kind: 'Panel', + spec: { + id: 1, + title: 'Panel with Prometheus datasource', + description: '', + links: [], + data: { + kind: 'QueryGroup', + spec: { + queries: [ + { + kind: 'PanelQuery', + spec: { + refId: 'A', + hidden: false, + query: { + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'grafana-prometheus-datasource', + datasource: { + name: 'gdev-prometheus', + }, + spec: { + expr: 'up', + }, + }, + }, + }, + ], + transformations: [], + queryOptions: {}, + }, + }, + vizConfig: { + kind: 'VizConfig', + group: 'timeseries', + version: '1.0.0', + spec: { + options: {}, + fieldConfig: { + defaults: {}, + overrides: [], + }, + }, + }, + }, + }, + 'panel-2': { + kind: 'Panel', + spec: { + id: 2, + title: 'Panel with TestData datasource', + description: '', + links: [], + data: { + kind: 'QueryGroup', + spec: { + queries: [ + { + kind: 'PanelQuery', + spec: { + refId: 'A', + hidden: false, + query: { + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'grafana-testdata-datasource', + datasource: { + name: 'gdev-testdata', + }, + spec: { + scenarioId: 'random_walk', + }, + }, + }, + }, + ], + transformations: [], + queryOptions: {}, + }, + }, + vizConfig: { + kind: 'VizConfig', + group: 'timeseries', + version: '1.0.0', + spec: { + options: {}, + fieldConfig: { + defaults: {}, + overrides: [], + }, + }, + }, + }, + }, + 'panel-3': { + kind: 'Panel', + spec: { + id: 3, + title: 'Panel with no datasource on query', + description: '', + links: [], + data: { + kind: 'QueryGroup', + spec: { + queries: [ + { + kind: 'PanelQuery', + spec: { + refId: 'A', + hidden: false, + query: { + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'grafana-testdata-datasource', + // No datasource.name - simulates panel with no explicit datasource + spec: {}, + }, + }, + }, + ], + transformations: [], + queryOptions: {}, + }, + }, + vizConfig: { + kind: 'VizConfig', + group: 'timeseries', + version: '1.0.0', + spec: { + options: {}, + fieldConfig: { + defaults: {}, + overrides: [], + }, + }, + }, + }, + }, + }, + layout: { + kind: 'GridLayout', + spec: { + items: [ + { + kind: 'GridLayoutItem', + spec: { + x: 0, + y: 0, + width: 12, + height: 8, + element: { kind: 'ElementReference', name: 'panel-1' }, + }, + }, + { + kind: 'GridLayoutItem', + spec: { + x: 12, + y: 0, + width: 12, + height: 8, + element: { kind: 'ElementReference', name: 'panel-2' }, + }, + }, + { + kind: 'GridLayoutItem', + spec: { + x: 0, + y: 8, + width: 12, + height: 8, + element: { kind: 'ElementReference', name: 'panel-3' }, + }, + }, + ], + }, + }, + }, + access: { + url: '/d/v2-dashboard-uid', + slug: 'v2-test-dashboard', + }, + apiVersion: 'v2', +}; From 359d097154a4334e01680247772a6cb072e56e7d Mon Sep 17 00:00:00 2001 From: Paul Marbach Date: Fri, 12 Dec 2025 16:57:47 -0500 Subject: [PATCH 088/297] Table: Remove hardcoded assumption of __nestedFrames field name (#115117) * Table: Remove hardcoded assumption of __nestedFrames field name * E2E for nested tables * Apply suggestion from @fastfrwrd --- .../panels-suite/table-kitchenSink.spec.ts | 27 +++++++++++++++++++ eslint-suppressions.json | 5 ---- .../src/selectors/components.ts | 3 +++ .../src/components/Table/TableNG/TableNG.tsx | 20 +++++++++++--- .../Table/TableNG/components/RowExpander.tsx | 11 +++++++- .../src/components/Table/TableNG/types.ts | 3 +-- .../src/components/Table/TableNG/utils.ts | 22 +++++++++------ .../datasource/tempo/resultTransformer.ts | 2 +- 8 files changed, 73 insertions(+), 20 deletions(-) diff --git a/e2e-playwright/panels-suite/table-kitchenSink.spec.ts b/e2e-playwright/panels-suite/table-kitchenSink.spec.ts index a14085aa753..6dddba81820 100644 --- a/e2e-playwright/panels-suite/table-kitchenSink.spec.ts +++ b/e2e-playwright/panels-suite/table-kitchenSink.spec.ts @@ -343,6 +343,33 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table'] // TODO -- saving for another day. }); + test('Tests nested table expansion', async ({ gotoDashboardPage, selectors, page }) => { + const dashboardPage = await gotoDashboardPage({ + uid: DASHBOARD_UID, + queryParams: new URLSearchParams({ editPanel: '4' }), + }); + + await expect( + dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Nested tables')) + ).toBeVisible(); + + await waitForTableLoad(page); + + await expect(page.locator('[role="row"]')).toHaveCount(3); // header + 2 rows + + const firstRowExpander = dashboardPage + .getByGrafanaSelector(selectors.components.Panels.Visualization.TableNG.RowExpander) + .first(); + + await firstRowExpander.click(); + await expect(page.locator('[role="row"]')).not.toHaveCount(3); // more rows are present now, it is dynamic tho. + + // TODO: test sorting + + await firstRowExpander.click(); + await expect(page.locator('[role="row"]')).toHaveCount(3); // back to original state + }); + test('Tests tooltip interactions', async ({ gotoDashboardPage, selectors }) => { const dashboardPage = await gotoDashboardPage({ uid: DASHBOARD_UID, diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 94a0b4c6b3c..2fcb6da5ee3 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -804,11 +804,6 @@ "count": 2 } }, - "packages/grafana-ui/src/components/Table/TableNG/utils.ts": { - "@typescript-eslint/consistent-type-assertions": { - "count": 1 - } - }, "packages/grafana-ui/src/components/Table/TableRT/Filter.tsx": { "@typescript-eslint/no-explicit-any": { "count": 1 diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index f1f2ce08642..0755477f93b 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -499,6 +499,9 @@ export const versionedComponents = { }, }, TableNG: { + RowExpander: { + '12.4.0': 'data-testid tableng row expander', + }, Filters: { HeaderButton: { '12.1.0': 'data-testid tableng header filter', diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index 0a7c69edbf3..f17a62b92cf 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -154,8 +154,18 @@ export function TableNG(props: TableNGProps) { const resizeHandler = useColumnResize(onColumnResize); - const rows = useMemo(() => frameToRecords(data), [data]); const hasNestedFrames = useMemo(() => getIsNestedTable(data.fields), [data]); + const nestedFramesFieldName = useMemo(() => { + if (!hasNestedFrames) { + return; + } + const firstNestedField = data.fields.find((f) => f.type === FieldType.nestedFrames); + if (!firstNestedField) { + return; + } + return getDisplayName(firstNestedField); + }, [data, hasNestedFrames]); + const rows = useMemo(() => frameToRecords(data, nestedFramesFieldName), [data, nestedFramesFieldName]); const getTextColorForBackground = useMemo(() => memoize(_getTextColorForBackground, { maxSize: 1000 }), []); const { @@ -374,7 +384,11 @@ export function TableNG(props: TableNGProps) { return null; } - const expandedRecords = applySort(frameToRecords(nestedData), nestedData.fields, sortColumns); + const expandedRecords = applySort( + frameToRecords(nestedData, nestedFramesFieldName), + nestedData.fields, + sortColumns + ); if (!expandedRecords.length) { return (
@@ -398,7 +412,7 @@ export function TableNG(props: TableNGProps) { width: COLUMN.EXPANDER_WIDTH, minWidth: COLUMN.EXPANDER_WIDTH, }), - [commonDataGridProps, data.fields.length, expandedRows, sortColumns, styles] + [commonDataGridProps, data.fields.length, expandedRows, sortColumns, styles, nestedFramesFieldName] ); const fromFields = useCallback( diff --git a/packages/grafana-ui/src/components/Table/TableNG/components/RowExpander.tsx b/packages/grafana-ui/src/components/Table/TableNG/components/RowExpander.tsx index d1f64824ef3..ab2ee41538d 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/components/RowExpander.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/components/RowExpander.tsx @@ -1,6 +1,7 @@ import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { t } from '@grafana/i18n'; import { useStyles2 } from '../../../../themes/ThemeContext'; @@ -16,13 +17,21 @@ export function RowExpander({ onCellExpand, isExpanded }: RowExpanderNGProps) { } } return ( -
+
diff --git a/packages/grafana-ui/src/components/Table/TableNG/types.ts b/packages/grafana-ui/src/components/Table/TableNG/types.ts index 3a641de5ac6..ddfaf189f34 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/types.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/types.ts @@ -79,7 +79,6 @@ export interface TableRow { // Nested table properties data?: DataFrame; - __nestedFrames?: DataFrame[]; __expanded?: boolean; // For row expansion state // Generic typing for column values @@ -262,7 +261,7 @@ export type TableCellStyles = (theme: GrafanaTheme2, options: TableCellStyleOpti export type Comparator = (a: TableCellValue, b: TableCellValue) => number; // Type for converting a DataFrame into an array of TableRows -export type FrameToRowsConverter = (frame: DataFrame) => TableRow[]; +export type FrameToRowsConverter = (frame: DataFrame, nestedFramesFieldName?: string) => TableRow[]; // Type for mapping column names to their field types export type ColumnTypes = Record; diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.ts index 0226f8b6463..b960d8c08c5 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/utils.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/utils.ts @@ -675,10 +675,12 @@ export function applySort( /** * @internal */ -export const frameToRecords = (frame: DataFrame): TableRow[] => { +export const frameToRecords = (frame: DataFrame, nestedFramesFieldName?: string): TableRow[] => { const fnBody = ` const rows = Array(frame.length); const values = frame.fields.map(f => f.values); + const hasNestedFrames = '${nestedFramesFieldName ?? ''}'.length > 0; + let rowCount = 0; for (let i = 0; i < frame.length; i++) { rows[rowCount] = { @@ -686,11 +688,14 @@ export const frameToRecords = (frame: DataFrame): TableRow[] => { __index: i, ${frame.fields.map((field, fieldIdx) => `${JSON.stringify(getDisplayName(field))}: values[${fieldIdx}][i]`).join(',')} }; - rowCount += 1; - if (rows[rowCount-1]['__nestedFrames']){ - const childFrame = rows[rowCount-1]['__nestedFrames']; - rows[rowCount] = {__depth: 1, __index: i, data: childFrame[0]} - rowCount += 1; + rowCount++; + + if (hasNestedFrames) { + const childFrame = rows[rowCount-1][${JSON.stringify(nestedFramesFieldName)}]; + if (childFrame){ + rows[rowCount] = {__depth: 1, __index: i, data: childFrame[0]} + rowCount++; + } } } return rows; @@ -698,8 +703,9 @@ export const frameToRecords = (frame: DataFrame): TableRow[] => { // Creates a function that converts a DataFrame into an array of TableRows // Uses new Function() for performance as it's faster than creating rows using loops - const convert = new Function('frame', fnBody) as FrameToRowsConverter; - return convert(frame); + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const convert = new Function('frame', 'nestedFramesFieldName', fnBody) as FrameToRowsConverter; + return convert(frame, nestedFramesFieldName); }; /* ----------------------------- Data grid comparator ---------------------------- */ diff --git a/public/app/plugins/datasource/tempo/resultTransformer.ts b/public/app/plugins/datasource/tempo/resultTransformer.ts index 01934cc97bf..87000026769 100644 --- a/public/app/plugins/datasource/tempo/resultTransformer.ts +++ b/public/app/plugins/datasource/tempo/resultTransformer.ts @@ -911,7 +911,7 @@ const traceSubFrame = ( subFrame.add(transformSpanToTraceData(span, spanSet, trace)); }); - return subFrame; + return toDataFrame(subFrame); }; interface TraceTableData { From 12b38d1b7a38336dab26164d0ec5736069c3fd08 Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Fri, 12 Dec 2025 23:14:48 +0100 Subject: [PATCH 089/297] Dashboards: Never allow rows with hidden header to be collapsed (#115284) Never allow rows with hidden header to be collapsed --- .../dashboard-scene/scene/layout-rows/RowItemRenderer.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx index 80c16b2f2a1..8d74ff46e3c 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx @@ -18,7 +18,8 @@ import { isDashboardLayoutGrid } from '../types/DashboardLayoutGrid'; import { RowItem } from './RowItem'; export function RowItemRenderer({ model }: SceneComponentProps) { - const { layout, collapse: isCollapsed, fillScreen, hideHeader: isHeaderHidden, isDropTarget, key } = model.useState(); + const { layout, collapse, fillScreen, hideHeader: isHeaderHidden, isDropTarget, key } = model.useState(); + const isCollapsed = collapse && !isHeaderHidden; // never allow a row without a header to be collapsed const isClone = isRepeatCloneOrChildOf(model); const { isEditing } = useDashboardState(model); const [isConditionallyHidden, conditionalRenderingClass, conditionalRenderingOverlay] = useIsConditionallyHidden( From 1bcccd5e6167b8914765d37d81ed8645d9c9ee9a Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Fri, 12 Dec 2025 17:26:28 -0500 Subject: [PATCH 090/297] Docs: Update export as JSON task (#115288) --- .../dashboards/share-dashboards-panels/_index.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/sources/visualizations/dashboards/share-dashboards-panels/_index.md b/docs/sources/visualizations/dashboards/share-dashboards-panels/_index.md index a2867759119..5fcd2344fe2 100644 --- a/docs/sources/visualizations/dashboards/share-dashboards-panels/_index.md +++ b/docs/sources/visualizations/dashboards/share-dashboards-panels/_index.md @@ -223,17 +223,25 @@ To export a dashboard in its current state as a PDF, follow these steps: 1. Click the **X** at the top-right corner to close the share drawer. -### Export a dashboard as JSON +### Export a dashboard as code Export a Grafana JSON file that contains everything you need, including layout, variables, styles, data sources, queries, and so on, so that you can later import the dashboard. To export a JSON file, follow these steps: 1. Click **Dashboards** in the main menu. 1. Open the dashboard you want to export. -1. Click the **Export** drop-down list in the top-right corner and select **Export as JSON**. +1. Click the **Export** drop-down list in the top-right corner and select **Export as code**. - The **Export dashboard JSON** drawer opens. + The **Export dashboard** drawer opens. + +1. Select the dashboard JSON model that you to export: + - **Classic** - Export dashboards created using the [current dashboard schema](https://grafana.com/docs/grafana//visualizations/dashboards/build-dashboards/view-dashboard-json-model/). + - **V1 Resource** - Export dashboards created using the [current dashboard schema](https://grafana.com/docs/grafana//visualizations/dashboards/build-dashboards/view-dashboard-json-model/) wrapped in the `spec` property of the [V1 Kubernetes-style resource](https://play.grafana.org/swagger?api=dashboard.grafana.app-v2alpha1). Choose between **JSON** and **YAML** format. + - **V2 Resource** - Export dashboards created using the [V2 Resource schema](https://play.grafana.org/swagger?api=dashboard.grafana.app-v2beta1). Choose between **JSON** and **YAML** format. + +1. Do one of the following: + - Toggle the **Export for sharing externally** switch to generate the JSON with a different data source UID. + - Toggle the **Remove deployment details** switch to make the dashboard externally shareable. -1. Toggle the **Export the dashboard to use in another instance** switch to generate the JSON with a different data source UID. 1. Click **Download file** or **Copy to clipboard**. 1. Click the **X** at the top-right corner to close the share drawer. From c5345498b16c0082c3d78d71b6c6547998667d49 Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Sat, 13 Dec 2025 00:42:48 +0000 Subject: [PATCH 091/297] I18n: Download translations from Crowdin (#115291) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 21 +++++++++++++++++---- public/locales/de-DE/grafana.json | 21 +++++++++++++++++---- public/locales/es-ES/grafana.json | 21 +++++++++++++++++---- public/locales/fr-FR/grafana.json | 21 +++++++++++++++++---- public/locales/hu-HU/grafana.json | 21 +++++++++++++++++---- public/locales/id-ID/grafana.json | 21 +++++++++++++++++---- public/locales/it-IT/grafana.json | 21 +++++++++++++++++---- public/locales/ja-JP/grafana.json | 21 +++++++++++++++++---- public/locales/ko-KR/grafana.json | 21 +++++++++++++++++---- public/locales/nl-NL/grafana.json | 21 +++++++++++++++++---- public/locales/pl-PL/grafana.json | 21 +++++++++++++++++---- public/locales/pt-BR/grafana.json | 21 +++++++++++++++++---- public/locales/pt-PT/grafana.json | 21 +++++++++++++++++---- public/locales/ru-RU/grafana.json | 21 +++++++++++++++++---- public/locales/sv-SE/grafana.json | 21 +++++++++++++++++---- public/locales/tr-TR/grafana.json | 21 +++++++++++++++++---- public/locales/zh-Hans/grafana.json | 21 +++++++++++++++++---- public/locales/zh-Hant/grafana.json | 21 +++++++++++++++++---- 18 files changed, 306 insertions(+), 72 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index af86db55162..316215abf0c 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -3739,6 +3739,10 @@ "clear": "Vymazat vyhledávání a filtry", "text": "Nebyly nalezeny žádné výsledky pro váš dotaz" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_one": "", @@ -5992,13 +5996,25 @@ "title-error-loading-dashboard": "Chyba při načítání nástěnky" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "Upravit panel", "view-panel": "Zobrazit panel" }, "title": { "dashboard": "Nástěnka", - "discard-changes-to-dashboard": "Zahodit změny nástěnky?" + "discard-changes-to-dashboard": "Zahodit změny nástěnky?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10798,7 +10814,6 @@ "title": "Nové" }, "new-dashboard": { - "empty-title": "", "title": "Nová nástěnka" }, "new-folder": { @@ -11958,7 +11973,6 @@ "title-setting-connection-could-cause-temporary-outage": "Nastavení tohoto připojení může způsobit dočasný výpadek" }, "getting-started-page": { - "header": "Zajišťování", "subtitle-provisioning-feature": "Zobrazujte a spravujte vazby zajištění" }, "git": { @@ -12730,7 +12744,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "Importovat", "new": "Nové", "new-dashboard": "Nová nástěnka", diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 57d5bcc26a9..26c780b333a 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -3707,6 +3707,10 @@ "clear": "Suche und Filter löschen", "text": "Keine Ergebnisse für deine Abfrage gefunden" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_one": "", @@ -5950,13 +5954,25 @@ "title-error-loading-dashboard": "Fehler beim Laden des Dashboards" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "Panel bearbeiten", "view-panel": "Panel anzeigen" }, "title": { "dashboard": "Dashboard", - "discard-changes-to-dashboard": "Änderungen am Dashboard verwerfen?" + "discard-changes-to-dashboard": "Änderungen am Dashboard verwerfen?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10712,7 +10728,6 @@ "title": "Neu" }, "new-dashboard": { - "empty-title": "", "title": "Neues Dashboard" }, "new-folder": { @@ -11856,7 +11871,6 @@ "title-setting-connection-could-cause-temporary-outage": "Das Einrichten dieser Verbindung kann zu einem vorübergehenden Ausfall führen" }, "getting-started-page": { - "header": "Bereitstellung", "subtitle-provisioning-feature": "Sehen und verwalten Sie Ihre Bereitstellungsverbindungen" }, "git": { @@ -12622,7 +12636,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "Importieren", "new": "Neu", "new-dashboard": "Neues Dashboard", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 9e141b42f4e..d0e83225d56 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -3707,6 +3707,10 @@ "clear": "Borrar la búsqueda y los filtros", "text": "No se han encontrado resultados para tu consulta" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_one": "", @@ -5950,13 +5954,25 @@ "title-error-loading-dashboard": "Error al cargar el panel de control" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "Editar panel", "view-panel": "Ver panel" }, "title": { "dashboard": "Panel de control", - "discard-changes-to-dashboard": "¿Descartar los cambios en el dashboard?" + "discard-changes-to-dashboard": "¿Descartar los cambios en el dashboard?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10712,7 +10728,6 @@ "title": "Nuevo" }, "new-dashboard": { - "empty-title": "", "title": "Nuevo panel de control" }, "new-folder": { @@ -11856,7 +11871,6 @@ "title-setting-connection-could-cause-temporary-outage": "Configurar esta conexión podría causar una interrupción temporal" }, "getting-started-page": { - "header": "Aprovisionamiento", "subtitle-provisioning-feature": "Ver y gestionar tus conexiones de aprovisionamiento" }, "git": { @@ -12622,7 +12636,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "Importar", "new": "Nuevo", "new-dashboard": "Nuevo panel de control", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 6adbf1a98d2..1944e137bb7 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -3707,6 +3707,10 @@ "clear": "Effacer la recherche et les filtres", "text": "Aucun résultat n'a été trouvé pour votre requête" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_one": "", @@ -5950,13 +5954,25 @@ "title-error-loading-dashboard": "Erreur lors du chargement du tableau de bord" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "Modifier le panneau", "view-panel": "Afficher le panneau" }, "title": { "dashboard": "Tableau de bord", - "discard-changes-to-dashboard": "Abandonner les modifications apportées au tableau de bord ?" + "discard-changes-to-dashboard": "Abandonner les modifications apportées au tableau de bord ?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10712,7 +10728,6 @@ "title": "Nouveau" }, "new-dashboard": { - "empty-title": "", "title": "Nouveau tableau de bord" }, "new-folder": { @@ -11856,7 +11871,6 @@ "title-setting-connection-could-cause-temporary-outage": "La configuration de cette connexion peut entraîner une interruption temporaire" }, "getting-started-page": { - "header": "Mise en service", "subtitle-provisioning-feature": "Afficher et gérer vos connexions de mise en service" }, "git": { @@ -12622,7 +12636,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "Importer", "new": "Nouveau", "new-dashboard": "Nouveau tableau de bord", diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index c96862e730a..b94b8cd7fe7 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -3707,6 +3707,10 @@ "clear": "Keresés és szűrők törlése", "text": "Nincs találat a lekérdezésre" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_one": "", @@ -5950,13 +5954,25 @@ "title-error-loading-dashboard": "Hiba történt az irányítópult betöltésekor" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "Panel szerkesztése", "view-panel": "Panel megtekintése" }, "title": { "dashboard": "Irányítópult", - "discard-changes-to-dashboard": "Elveti az irányítópult módosításait?" + "discard-changes-to-dashboard": "Elveti az irányítópult módosításait?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10712,7 +10728,6 @@ "title": "Új" }, "new-dashboard": { - "empty-title": "", "title": "Új irányítópult" }, "new-folder": { @@ -11856,7 +11871,6 @@ "title-setting-connection-could-cause-temporary-outage": "A kapcsolat létrehozása ideiglenes üzemszünetet okozhat" }, "getting-started-page": { - "header": "Kiépítés", "subtitle-provisioning-feature": "Kiépítési kapcsolatok megtekintése és kezelése" }, "git": { @@ -12622,7 +12636,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "Importálás", "new": "Új", "new-dashboard": "Új irányítópult", diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 365e4e2f783..5f1dda49d0a 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -3691,6 +3691,10 @@ "clear": "Hapus pencarian dan filter", "text": "Hasil untuk kueri Anda tidak ditemukan" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_other": "", @@ -5929,13 +5933,25 @@ "title-error-loading-dashboard": "Kesalahan saat memuat dasbor" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "Edit panel", "view-panel": "Lihat panel" }, "title": { "dashboard": "Dasbor", - "discard-changes-to-dashboard": "Batalkan perubahan ke dasbor?" + "discard-changes-to-dashboard": "Batalkan perubahan ke dasbor?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10669,7 +10685,6 @@ "title": "Baru" }, "new-dashboard": { - "empty-title": "", "title": "Dasbor baru" }, "new-folder": { @@ -11805,7 +11820,6 @@ "title-setting-connection-could-cause-temporary-outage": "Mengatur koneksi ini dapat menyebabkan pemadaman sementara" }, "getting-started-page": { - "header": "Penyediaan", "subtitle-provisioning-feature": "Lihat dan kelola koneksi penyediaan Anda" }, "git": { @@ -12568,7 +12582,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "Impor", "new": "Baru", "new-dashboard": "Dasbor baru", diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 0f1dbd3b5fb..c8b728faa0f 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -3707,6 +3707,10 @@ "clear": "Cancella ricerca e filtri", "text": "Nessun risultato trovato per la ricerca" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_one": "", @@ -5950,13 +5954,25 @@ "title-error-loading-dashboard": "Errore durante il caricamento del dashboard" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "Modifica pannello", "view-panel": "Visualizza pannello" }, "title": { "dashboard": "Dashboard", - "discard-changes-to-dashboard": "Annullare le modifiche alla dashboard?" + "discard-changes-to-dashboard": "Annullare le modifiche alla dashboard?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10712,7 +10728,6 @@ "title": "Nuovo" }, "new-dashboard": { - "empty-title": "", "title": "Nuovo dashboard" }, "new-folder": { @@ -11856,7 +11871,6 @@ "title-setting-connection-could-cause-temporary-outage": "La configurazione di questa connessione potrebbe causare un'interruzione temporanea" }, "getting-started-page": { - "header": "Provisioning", "subtitle-provisioning-feature": "Visualizza e gestisci le connessioni di provisioning" }, "git": { @@ -12622,7 +12636,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "Importa", "new": "Nuovo", "new-dashboard": "Nuovo dashboard", diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index e6e8b87e70d..d633cd80893 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -3691,6 +3691,10 @@ "clear": "検索とフィルタをクリア", "text": "クエリに一致する結果が見つかりませんでした。" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_other": "", @@ -5929,13 +5933,25 @@ "title-error-loading-dashboard": "ダッシュボードの読み込み中にエラーが発生しました" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "パネルを編集", "view-panel": "パネルを表示" }, "title": { "dashboard": "ダッシュボード", - "discard-changes-to-dashboard": "ダッシュボードへの変更を破棄しますか?" + "discard-changes-to-dashboard": "ダッシュボードへの変更を破棄しますか?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10669,7 +10685,6 @@ "title": "新規" }, "new-dashboard": { - "empty-title": "", "title": "新しいダッシュボード" }, "new-folder": { @@ -11805,7 +11820,6 @@ "title-setting-connection-could-cause-temporary-outage": "この接続設定を行うことで、一時的に停止する可能性があります" }, "getting-started-page": { - "header": "プロビジョニング", "subtitle-provisioning-feature": "プロビジョニング接続を表示・管理" }, "git": { @@ -12568,7 +12582,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "インポート", "new": "新規", "new-dashboard": "新しいダッシュボード", diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 152cb7f1607..64bab4a6953 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -3691,6 +3691,10 @@ "clear": "검색 및 필터 초기화", "text": "쿼리에 대해 찾은 결과 없음" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_other": "", @@ -5929,13 +5933,25 @@ "title-error-loading-dashboard": "대시보드 로딩 중 오류 발생" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "패널 편집", "view-panel": "패널 보기" }, "title": { "dashboard": "대시보드", - "discard-changes-to-dashboard": "대시보드 변경 사항을 취소하시겠어요?" + "discard-changes-to-dashboard": "대시보드 변경 사항을 취소하시겠어요?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10669,7 +10685,6 @@ "title": "신규" }, "new-dashboard": { - "empty-title": "", "title": "새 대시보드" }, "new-folder": { @@ -11805,7 +11820,6 @@ "title-setting-connection-could-cause-temporary-outage": "이 연결을 설정하면 일시적인 중단이 발생할 수 있습니다" }, "getting-started-page": { - "header": "프로비저닝", "subtitle-provisioning-feature": "프로비저닝 연결 보기 및 관리" }, "git": { @@ -12568,7 +12582,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "가져오기", "new": "신규", "new-dashboard": "새 대시보드", diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index a20a4e079af..cd16286eea8 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -3707,6 +3707,10 @@ "clear": "Zoekopdracht en filters wissen", "text": "Geen resultaten gevonden voor je zoekopdracht" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_one": "", @@ -5950,13 +5954,25 @@ "title-error-loading-dashboard": "Er is een fout opgetreden bij het laden van het dashboard" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "Paneel bewerken", "view-panel": "Paneel bekijken" }, "title": { "dashboard": "Dashboard", - "discard-changes-to-dashboard": "Wijzigingen in dashboard verwerpen?" + "discard-changes-to-dashboard": "Wijzigingen in dashboard verwerpen?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10712,7 +10728,6 @@ "title": "Nieuw" }, "new-dashboard": { - "empty-title": "", "title": "Nieuw dashboard" }, "new-folder": { @@ -11856,7 +11871,6 @@ "title-setting-connection-could-cause-temporary-outage": "Het opzetten van deze verbinding kan een tijdelijke storing veroorzaken" }, "getting-started-page": { - "header": "Provisioning", "subtitle-provisioning-feature": "Je provisioningverbindingen bekijken en beheren" }, "git": { @@ -12622,7 +12636,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "Importeren", "new": "Nieuw", "new-dashboard": "Nieuw dashboard", diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 06987de8041..989a1d9be78 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -3739,6 +3739,10 @@ "clear": "Wyczyść wyszukiwanie i filtry", "text": "Nie znaleziono wyników dla tego zapytania" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_one": "", @@ -5992,13 +5996,25 @@ "title-error-loading-dashboard": "Błąd wczytywania pulpitu" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "Edytuj panel", "view-panel": "Wyświetl panel" }, "title": { "dashboard": "Pulpit", - "discard-changes-to-dashboard": "Odrzucić zmiany dotyczące pulpitu?" + "discard-changes-to-dashboard": "Odrzucić zmiany dotyczące pulpitu?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10798,7 +10814,6 @@ "title": "Nowy" }, "new-dashboard": { - "empty-title": "", "title": "Nowy pulpit" }, "new-folder": { @@ -11958,7 +11973,6 @@ "title-setting-connection-could-cause-temporary-outage": "Skonfigurowanie tego połączenia może spowodować tymczasową niedostępność" }, "getting-started-page": { - "header": "Konfiguracja", "subtitle-provisioning-feature": "Wyświetlaj połączenia aprowizacyjne i nimi zarządzaj" }, "git": { @@ -12730,7 +12744,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "Importuj", "new": "Nowy", "new-dashboard": "Nowy pulpit", diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 0774fe6767e..5480b73ca27 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -3707,6 +3707,10 @@ "clear": "Limpar busca e filtros", "text": "Nenhum resultado encontrado para sua consulta" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_one": "", @@ -5950,13 +5954,25 @@ "title-error-loading-dashboard": "Erro ao carregar o painel de controle" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "Editar painel", "view-panel": "Visualizar painel" }, "title": { "dashboard": "Painel de controle", - "discard-changes-to-dashboard": "Deseja descartar as alterações no painel?" + "discard-changes-to-dashboard": "Deseja descartar as alterações no painel?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10712,7 +10728,6 @@ "title": "Novo" }, "new-dashboard": { - "empty-title": "", "title": "Novo painel de controle" }, "new-folder": { @@ -11856,7 +11871,6 @@ "title-setting-connection-could-cause-temporary-outage": "Estabelecer esta conexão pode causar uma interrupção temporária" }, "getting-started-page": { - "header": "Aprovisionamento", "subtitle-provisioning-feature": "Visualize e gerencie suas conexões de provisionamento" }, "git": { @@ -12622,7 +12636,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "Importar", "new": "Novo", "new-dashboard": "Novo painel de controle", diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index eabb3d9e99e..53cf4c8fb46 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -3707,6 +3707,10 @@ "clear": "Limpar a pesquisa e os filtros", "text": "Não foram encontrados resultados para a sua consulta" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_one": "", @@ -5950,13 +5954,25 @@ "title-error-loading-dashboard": "Erro ao carregar o painel de controlo" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "Editar painel", "view-panel": "Visualizar painel" }, "title": { "dashboard": "Painel de controlo", - "discard-changes-to-dashboard": "Rejeitar alterações no painel de controlo?" + "discard-changes-to-dashboard": "Rejeitar alterações no painel de controlo?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10712,7 +10728,6 @@ "title": "Novo" }, "new-dashboard": { - "empty-title": "", "title": "Novo painel de controlo" }, "new-folder": { @@ -11856,7 +11871,6 @@ "title-setting-connection-could-cause-temporary-outage": "Configurar esta ligação pode causar uma interrupção temporária" }, "getting-started-page": { - "header": "Provisionamento", "subtitle-provisioning-feature": "Ver e gerir as suas ligações de provisionamento" }, "git": { @@ -12622,7 +12636,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "Importar", "new": "Novo", "new-dashboard": "Novo painel de controlo", diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index 1542c4a7a29..a4236136a35 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -3739,6 +3739,10 @@ "clear": "Очистить поиск и фильтры", "text": "По вашему запросу ничего не найдено" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_one": "", @@ -5992,13 +5996,25 @@ "title-error-loading-dashboard": "Ошибка при загрузке дашборда" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "Редактировать панель", "view-panel": "Просмотр панели" }, "title": { "dashboard": "Дашборд", - "discard-changes-to-dashboard": "Отменить изменения на дашборде?" + "discard-changes-to-dashboard": "Отменить изменения на дашборде?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10798,7 +10814,6 @@ "title": "Новые элементы" }, "new-dashboard": { - "empty-title": "", "title": "Новый дашборд" }, "new-folder": { @@ -11958,7 +11973,6 @@ "title-setting-connection-could-cause-temporary-outage": "Настройка этого подключения может привести к временному сбою" }, "getting-started-page": { - "header": "Подготовка к работе", "subtitle-provisioning-feature": "Просмотр подключений для подготовки и управлением ими" }, "git": { @@ -12730,7 +12744,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "Импорт", "new": "Новые элементы", "new-dashboard": "Новый дашборд", diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index c390b4175c2..13f96c68f3e 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -3707,6 +3707,10 @@ "clear": "Rensa sökning och filter", "text": "Inga resultat hittades för din fråga" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_one": "", @@ -5950,13 +5954,25 @@ "title-error-loading-dashboard": "Fel vid laddning av instrumentpanel" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "Redigera panel", "view-panel": "Visa panel" }, "title": { "dashboard": "Instrumentpanel", - "discard-changes-to-dashboard": "Kassera ändringar i instrumentpanelen?" + "discard-changes-to-dashboard": "Kassera ändringar i instrumentpanelen?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10712,7 +10728,6 @@ "title": "Nyhet" }, "new-dashboard": { - "empty-title": "", "title": "Ny instrumentpanel" }, "new-folder": { @@ -11856,7 +11871,6 @@ "title-setting-connection-could-cause-temporary-outage": "Konfiguration av den här anslutningen kan orsaka ett tillfälligt avbrott" }, "getting-started-page": { - "header": "Provisionering", "subtitle-provisioning-feature": "Visa och hantera dina provisioneringsanslutningar" }, "git": { @@ -12622,7 +12636,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "Importera", "new": "Nyhet", "new-dashboard": "Ny instrumentpanel", diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 258d74c27f1..96b92518f07 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -3707,6 +3707,10 @@ "clear": "Aramayı ve filtreleri temizle", "text": "Sorgunuz için sonuç bulunamadı" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_one": "", @@ -5950,13 +5954,25 @@ "title-error-loading-dashboard": "Pano yüklenirken hata oluştu" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "Paneli düzenle", "view-panel": "Paneli görüntüle" }, "title": { "dashboard": "Pano", - "discard-changes-to-dashboard": "Panodaki değişiklikler silinsin mi?" + "discard-changes-to-dashboard": "Panodaki değişiklikler silinsin mi?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10712,7 +10728,6 @@ "title": "Yeni" }, "new-dashboard": { - "empty-title": "", "title": "Yeni pano" }, "new-folder": { @@ -11856,7 +11871,6 @@ "title-setting-connection-could-cause-temporary-outage": "Bu bağlantıyı kurmak geçici bir kesintiye neden olabilir" }, "getting-started-page": { - "header": "Sağlama", "subtitle-provisioning-feature": "Sağlama bağlantılarınızı görüntüleyin ve yönetin" }, "git": { @@ -12622,7 +12636,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "İçe aktar", "new": "Yeni", "new-dashboard": "Yeni pano", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index e2514cf36ed..87666d55d39 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -3691,6 +3691,10 @@ "clear": "清除搜索和筛选条件", "text": "未找到与您的查询相关的结果" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_other": "", @@ -5929,13 +5933,25 @@ "title-error-loading-dashboard": "加载数据面板时出错" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "编辑面板", "view-panel": "查看面板" }, "title": { "dashboard": "仪表板", - "discard-changes-to-dashboard": "放弃对数据面板的更改?" + "discard-changes-to-dashboard": "放弃对数据面板的更改?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10669,7 +10685,6 @@ "title": "新建" }, "new-dashboard": { - "empty-title": "", "title": "新建仪表板" }, "new-folder": { @@ -11805,7 +11820,6 @@ "title-setting-connection-could-cause-temporary-outage": "设置此连接可能会导致暂时中断" }, "getting-started-page": { - "header": "配置", "subtitle-provisioning-feature": "查看和管理您的预配连接" }, "git": { @@ -12568,7 +12582,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "导入", "new": "新建", "new-dashboard": "新建仪表板", diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index 7ff6bfff111..d752e8ef1c1 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -3691,6 +3691,10 @@ "clear": "清除搜尋和篩選條件", "text": "未找到您的查詢結果" }, + "recently-viewed": { + "empty": "", + "title": "" + }, "restore": { "success": "", "all-failed_other": "", @@ -5929,13 +5933,25 @@ "title-error-loading-dashboard": "載入控制面板發生錯誤" }, "dashboard-scene": { + "modal": { + "cancel": "", + "discard": "", + "save": "", + "text": { + "save-changes-question": "" + }, + "title": { + "unsaved-changes": "" + } + }, "text": { "edit-panel": "編輯面板", "view-panel": "檢視面板" }, "title": { "dashboard": "儀表板", - "discard-changes-to-dashboard": "要捨棄儀表板的變更嗎?" + "discard-changes-to-dashboard": "要捨棄儀表板的變更嗎?", + "unsaved-changes-question": "" } }, "dashboard-scene-page-state-manager": { @@ -10669,7 +10685,6 @@ "title": "新" }, "new-dashboard": { - "empty-title": "", "title": "新儀表板" }, "new-folder": { @@ -11805,7 +11820,6 @@ "title-setting-connection-could-cause-temporary-outage": "設定此連線可能會導致暫時中斷" }, "getting-started-page": { - "header": "佈建", "subtitle-provisioning-feature": "檢視及管理您的佈建連線" }, "git": { @@ -12568,7 +12582,6 @@ } }, "dashboard-actions": { - "empty-dashboard": "", "import": "匯入", "new": "新", "new-dashboard": "新儀表板", From 0c49337205e744be8a197a1167b7e5227f74d064 Mon Sep 17 00:00:00 2001 From: Gonzalo Trigueros Manzanas <242162051+gttrigger@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:22:33 +0100 Subject: [PATCH 092/297] Provisioning: add warning column to JobSummary UI. (#115220) --- public/app/features/provisioning/Job/JobSummary.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/app/features/provisioning/Job/JobSummary.tsx b/public/app/features/provisioning/Job/JobSummary.tsx index 0f9ba17db08..0dfecd9a616 100644 --- a/public/app/features/provisioning/Job/JobSummary.tsx +++ b/public/app/features/provisioning/Job/JobSummary.tsx @@ -33,6 +33,11 @@ const getSummaryColumns = () => [ header: 'Unchanged', cell: ({ row: { original: item } }: SummaryCell) => item.noop?.toString() || '-', }, + { + id: 'warnings', + header: 'Warnings', + cell: ({ row: { original: item } }: SummaryCell) => item.warning?.toString() || '-', + }, { id: 'errors', header: 'Errors', From 4149767391a7855faa881743a6d85a3adca160f7 Mon Sep 17 00:00:00 2001 From: Joe Elliott Date: Mon, 15 Dec 2025 04:41:24 -0500 Subject: [PATCH 093/297] Tempo: Correctly escape/unescape tag when looking for tag values (#114275) * Correctly escape/unescape tag Signed-off-by: Joe Elliott * changelog Signed-off-by: Joe Elliott * Revert "changelog" This reverts commit e0cde18994c67fbdd601514d2f930798b0ae76c6. --------- Signed-off-by: Joe Elliott --- pkg/tsdb/tempo/tempo.go | 10 +++++++++- .../app/plugins/datasource/tempo/language_provider.ts | 4 +--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/tsdb/tempo/tempo.go b/pkg/tsdb/tempo/tempo.go index 1224c3b7029..cb797184a60 100644 --- a/pkg/tsdb/tempo/tempo.go +++ b/pkg/tsdb/tempo/tempo.go @@ -280,7 +280,15 @@ func (s *Service) handleTagValues(rw http.ResponseWriter, req *http.Request) { return } - tempoPath := fmt.Sprintf("api/v2/search/tag/%s/values", encodedTag) + // escape tag + tag, err := url.PathUnescape(encodedTag) + if err != nil { + s.logger.Error("Failed to unescape", "error", err, "tag", encodedTag) + http.Error(rw, "Invalid 'tag' parameter", http.StatusBadRequest) + return + } + + tempoPath := fmt.Sprintf("api/v2/search/tag/%s/values", tag) s.proxyToTempo(rw, req, tempoPath) } diff --git a/public/app/plugins/datasource/tempo/language_provider.ts b/public/app/plugins/datasource/tempo/language_provider.ts index f17ec67790f..ce28941e433 100644 --- a/public/app/plugins/datasource/tempo/language_provider.ts +++ b/public/app/plugins/datasource/tempo/language_provider.ts @@ -190,9 +190,7 @@ export default class TempoLanguageProvider extends LanguageProvider { * @returns the encoded tag */ private encodeTag = (tag: string): string => { - // If we call `encodeURIComponent` only once, we still get an error when issuing a request to the backend - // Reference: https://stackoverflow.com/a/37456192 - return encodeURIComponent(encodeURIComponent(tag)); + return encodeURIComponent(tag); }; generateQueryFromFilters({ From 074e8ce1283eef913c11d9c78ef4bf323766f80b Mon Sep 17 00:00:00 2001 From: Robert Horvath Date: Mon, 15 Dec 2025 10:59:24 +0100 Subject: [PATCH 094/297] Chore: fix grafana 12 release and support dates (#115235) fix release and support dates of grafana 12.3.x and 12.4.x --- docs/sources/upgrade-guide/when-to-upgrade/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/upgrade-guide/when-to-upgrade/index.md b/docs/sources/upgrade-guide/when-to-upgrade/index.md index e7a29e531e0..53d8c23330e 100644 --- a/docs/sources/upgrade-guide/when-to-upgrade/index.md +++ b/docs/sources/upgrade-guide/when-to-upgrade/index.md @@ -107,8 +107,8 @@ Here is an overview of version support through 2026: | 12.0.x | May 5, 2025 | February 5, 2026 | Patch Support | | 12.1.x | July 22, 2025 | April 22, 2026 | Patch Support | | 12.2.x | September 23, 2025 | June 23, 2026 | Patch Support | -| 12.3.x | November 18, 2025 | August 18, 2026 | Yet to be released | -| 12.4.x (Last minor of 12) | February 24, 2026 | November 24, 2026 | Yet to be released | +| 12.3.x | November 19, 2025 | August 19, 2026 | Patch Support | +| 12.4.x (Last minor of 12) | February 24, 2026 | May 24, 2027 | Yet to be released | | 13.0.0 | TBD | TBD | Yet to be released | ## How are these versions supported? From a3d7bd8dca8f3ace536cc28235d3b9a0e1d521cd Mon Sep 17 00:00:00 2001 From: "Marc M." <146180665+grafakus@users.noreply.github.com> Date: Mon, 15 Dec 2025 11:05:36 +0100 Subject: [PATCH 095/297] DynamicDashboards: In view mode, hide config button when panel has not been configured (#115261) --- .../scene/UnconfiguredPanel.tsx | 46 ++++++++++++++----- public/locales/en-US/grafana.json | 1 + 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/UnconfiguredPanel.tsx b/public/app/features/dashboard-scene/scene/UnconfiguredPanel.tsx index 2be7201ad74..44a15a0d3e1 100644 --- a/public/app/features/dashboard-scene/scene/UnconfiguredPanel.tsx +++ b/public/app/features/dashboard-scene/scene/UnconfiguredPanel.tsx @@ -5,7 +5,19 @@ import { CoreApp, GrafanaTheme2, PanelPlugin, PanelProps } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { config, locationService } from '@grafana/runtime'; import { sceneUtils } from '@grafana/scenes'; -import { Box, Button, ButtonGroup, Dropdown, Icon, Menu, Stack, Text, usePanelContext, useStyles2 } from '@grafana/ui'; +import { + Box, + Button, + ButtonGroup, + Dropdown, + EmptyState, + Icon, + Menu, + Stack, + Text, + usePanelContext, + useStyles2, +} from '@grafana/ui'; import { NEW_PANEL_TITLE } from '../../dashboard/utils/dashboard'; import { DashboardInteractions } from '../utils/interactions'; @@ -92,20 +104,30 @@ function UnconfiguredPanelComp(props: PanelProps) { ); } + const { isEditing } = dashboard.state; + return ( - - - - + + + ); }; From d1ef2837fc69158fc3d2fdc5b4dde869cf2394bd Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:04:19 +0000 Subject: [PATCH 144/297] I18n: Download translations from Crowdin (#115383) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 6 +++++- public/locales/de-DE/grafana.json | 6 +++++- public/locales/es-ES/grafana.json | 6 +++++- public/locales/fr-FR/grafana.json | 6 +++++- public/locales/hu-HU/grafana.json | 6 +++++- public/locales/id-ID/grafana.json | 6 +++++- public/locales/it-IT/grafana.json | 6 +++++- public/locales/ja-JP/grafana.json | 6 +++++- public/locales/ko-KR/grafana.json | 6 +++++- public/locales/nl-NL/grafana.json | 6 +++++- public/locales/pl-PL/grafana.json | 6 +++++- public/locales/pt-BR/grafana.json | 6 +++++- public/locales/pt-PT/grafana.json | 6 +++++- public/locales/ru-RU/grafana.json | 6 +++++- public/locales/sv-SE/grafana.json | 6 +++++- public/locales/tr-TR/grafana.json | 6 +++++- public/locales/zh-Hans/grafana.json | 6 +++++- public/locales/zh-Hant/grafana.json | 6 +++++- 18 files changed, 90 insertions(+), 18 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 316215abf0c..c64175c4d1f 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -5171,6 +5171,7 @@ "empty-state-message": "", "menu-open-panel-editor": "Konfigurovat", "menu-use-library-panel": "Použít panel knihovny", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6187,7 +6188,10 @@ "no-data-found": "Nebyla nalezena žádná data" }, "inspect-json-tab": { - "apply": "Použít" + "apply": "Použít", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "Dynamicky vypočítá interval vydělením časového rozsahu zadaným počtem", diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 26c780b333a..0da32e80f94 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -5133,6 +5133,7 @@ "empty-state-message": "", "menu-open-panel-editor": "Konfigurieren", "menu-use-library-panel": "Bibliotheks-Panel verwenden", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6145,7 +6146,10 @@ "no-data-found": "Keine Daten gefunden" }, "inspect-json-tab": { - "apply": "Anwenden" + "apply": "Anwenden", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "Berechnet dynamisch das Intervall, indem der Zeitbereich durch die angegebene Anzahl dividiert wird", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index d0e83225d56..c44e8c8ff3b 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -5133,6 +5133,7 @@ "empty-state-message": "", "menu-open-panel-editor": "Configurar", "menu-use-library-panel": "Usar panel de la librería", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6145,7 +6146,10 @@ "no-data-found": "No se han encontrado datos" }, "inspect-json-tab": { - "apply": "Aplicar" + "apply": "Aplicar", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "Calcula dinámicamente el intervalo dividiendo el intervalo de tiempo por el recuento especificado", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 1944e137bb7..aecae73acab 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -5133,6 +5133,7 @@ "empty-state-message": "", "menu-open-panel-editor": "Configurer", "menu-use-library-panel": "Utiliser le panneau de bibliothèque", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6145,7 +6146,10 @@ "no-data-found": "Aucune donnée n’a été trouvée" }, "inspect-json-tab": { - "apply": "Appliquer" + "apply": "Appliquer", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "Calcule dynamiquement l’intervalle en divisant la plage de temps par le nombre spécifié", diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index b94b8cd7fe7..7f049004022 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -5133,6 +5133,7 @@ "empty-state-message": "", "menu-open-panel-editor": "Konfigurálás", "menu-use-library-panel": "Könyvtárpanel használata", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6145,7 +6146,10 @@ "no-data-found": "Nem található adat" }, "inspect-json-tab": { - "apply": "Alkalmaz" + "apply": "Alkalmaz", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "Dinamikusan kiszámítja az intervallumot úgy, hogy az időtartományt elosztja a megadott számmal", diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 5f1dda49d0a..1676500a4b3 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -5114,6 +5114,7 @@ "empty-state-message": "", "menu-open-panel-editor": "Konfigurasikan", "menu-use-library-panel": "Gunakan panel pustaka", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6124,7 +6125,10 @@ "no-data-found": "Tidak ada data yang ditemukan" }, "inspect-json-tab": { - "apply": "Terapkan" + "apply": "Terapkan", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "Menghitung interval secara dinamis dengan membagi rentang waktu dengan jumlah yang ditentukan", diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index c8b728faa0f..ef4e977fca8 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -5133,6 +5133,7 @@ "empty-state-message": "", "menu-open-panel-editor": "Configura", "menu-use-library-panel": "Usa il pannello della libreria", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6145,7 +6146,10 @@ "no-data-found": "Nessun dato trovato" }, "inspect-json-tab": { - "apply": "Applica" + "apply": "Applica", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "Calcola dinamicamente l'intervallo dividendo l'intervallo di tempo per il conteggio specificato", diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index d633cd80893..feb94f34766 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -5114,6 +5114,7 @@ "empty-state-message": "", "menu-open-panel-editor": "構成", "menu-use-library-panel": "ライブラリパネルを使用", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6124,7 +6125,10 @@ "no-data-found": "データが見つかりません" }, "inspect-json-tab": { - "apply": "適用" + "apply": "適用", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "指定した数で時間範囲を分割して、間隔を動的に計算します", diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 64bab4a6953..24ea525fbb6 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -5114,6 +5114,7 @@ "empty-state-message": "", "menu-open-panel-editor": "구성", "menu-use-library-panel": "라이브러리 패널 사용", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6124,7 +6125,10 @@ "no-data-found": "데이터를 찾을 수 없음" }, "inspect-json-tab": { - "apply": "적용" + "apply": "적용", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "시간 범위를 지정된 수로 나누어 동적으로 간격을 계산합니다.", diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index cd16286eea8..facfe8cee8e 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -5133,6 +5133,7 @@ "empty-state-message": "", "menu-open-panel-editor": "Configureren", "menu-use-library-panel": "Bibliotheekpaneel gebruiken", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6145,7 +6146,10 @@ "no-data-found": "Geen info gevonden" }, "inspect-json-tab": { - "apply": "Toepassen" + "apply": "Toepassen", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "Berekent het interval dynamisch door het tijdsbereik te delen door het opgegeven aantal", diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 989a1d9be78..98cfa899c2e 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -5171,6 +5171,7 @@ "empty-state-message": "", "menu-open-panel-editor": "Konfiguruj", "menu-use-library-panel": "Użyj panelu biblioteki", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6187,7 +6188,10 @@ "no-data-found": "Nie znaleziono danych" }, "inspect-json-tab": { - "apply": "Zastosuj" + "apply": "Zastosuj", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "Dynamicznie oblicza odstęp czasu, dzieląc zakres przez określoną liczbę", diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 5480b73ca27..542bd14c8a1 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -5133,6 +5133,7 @@ "empty-state-message": "", "menu-open-panel-editor": "Configuração", "menu-use-library-panel": "Usar painel da biblioteca", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6145,7 +6146,10 @@ "no-data-found": "Nenhum dado encontrado" }, "inspect-json-tab": { - "apply": "Aplicar" + "apply": "Aplicar", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "Calcula dinamicamente o intervalo ao dividir o intervalo de tempo pela contagem especificada", diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index 53cf4c8fb46..30c132e560d 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -5133,6 +5133,7 @@ "empty-state-message": "", "menu-open-panel-editor": "Configurar", "menu-use-library-panel": "Utilizar o painel de biblioteca", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6145,7 +6146,10 @@ "no-data-found": "Nenhum dado encontrado" }, "inspect-json-tab": { - "apply": "Aplicar" + "apply": "Aplicar", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "Calcula dinamicamente o intervalo ao dividir o intervalo de tempo pela contagem especificada", diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index a4236136a35..ade7a64fe21 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -5171,6 +5171,7 @@ "empty-state-message": "", "menu-open-panel-editor": "Настроить", "menu-use-library-panel": "Использовать панель библиотеки", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6187,7 +6188,10 @@ "no-data-found": "Данные не найдены" }, "inspect-json-tab": { - "apply": "Применить" + "apply": "Применить", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "Динамически рассчитывает интервал путем деления временного диапазона на указанное количество", diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 13f96c68f3e..45669bf75c5 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -5133,6 +5133,7 @@ "empty-state-message": "", "menu-open-panel-editor": "Konfigurera", "menu-use-library-panel": "Använd bibliotekspanel", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6145,7 +6146,10 @@ "no-data-found": "Inga data hittades" }, "inspect-json-tab": { - "apply": "Tillämpa" + "apply": "Tillämpa", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "Beräknar intervallet dynamiskt genom att dividera tidsintervallet med det angivna antalet", diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 96b92518f07..2277af7feb5 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -5133,6 +5133,7 @@ "empty-state-message": "", "menu-open-panel-editor": "Yapılandır", "menu-use-library-panel": "Kütüphane panelini kullan", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6145,7 +6146,10 @@ "no-data-found": "Veri bulunamadı" }, "inspect-json-tab": { - "apply": "Uygula" + "apply": "Uygula", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "Zaman aralığını belirtilen sayıya bölerek aralığı dinamik olarak hesaplar", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 87666d55d39..c6a34de778b 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -5114,6 +5114,7 @@ "empty-state-message": "", "menu-open-panel-editor": "配置", "menu-use-library-panel": "使用库面板", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6124,7 +6125,10 @@ "no-data-found": "未找到数据" }, "inspect-json-tab": { - "apply": "应用" + "apply": "应用", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "通过将时间范围除以指定的计数来动态计算间隔", diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index d752e8ef1c1..4a569b10b0f 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -5114,6 +5114,7 @@ "empty-state-message": "", "menu-open-panel-editor": "設定", "menu-use-library-panel": "使用資料庫面板", + "missing-config": "", "suggestions": { "empty-state-message": "" } @@ -6124,7 +6125,10 @@ "no-data-found": "找不到數據" }, "inspect-json-tab": { - "apply": "套用" + "apply": "套用", + "error-invalid-json": "", + "error-invalid-v2-panel": "", + "validation-error": "" }, "interval-variable-form": { "description-auto-option": "透過將時間範圍除以指定的計數來動態計算間隔", From fea972cb11496a14534272baef9aa6bbc8c5f5a3 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 16 Dec 2025 14:11:46 +0300 Subject: [PATCH 145/297] Dashboards: Avoid infra/log in apps (#115396) --- .golangci.yml | 20 +++++++++---- apps/dashboard/go.mod | 2 +- .../pkg/migration/schemaversion/cache.go | 28 ++++++++----------- .../pkg/migration/schemaversion/cache_test.go | 24 ++++++++-------- .../schemaversion/datasource_utils.go | 6 ++-- 5 files changed, 42 insertions(+), 38 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 3b7871662e2..b52a986d435 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,7 +3,7 @@ # Others can set up the YAML LSP manually, which supports schemas: https://github.com/redhat-developer/yaml-language-server # $schema: https://golangci-lint.run/jsonschema/golangci.jsonschema.json -version: "2" +version: '2' run: timeout: 15m concurrency: 10 @@ -83,6 +83,16 @@ linters: deny: - pkg: github.com/grafana/grafana/pkg desc: apps/playlist is not allowed to import grafana core + apps-dashboard: + list-mode: lax + files: + - ./apps/dashboard/* + - ./apps/dashboard/**/* + allow: + - github.com/grafana/grafana/pkg/apimachinery + deny: + - pkg: github.com/grafana/grafana/pkg + desc: apps/dashboard is not allowed to import grafana core apps-secret: list-mode: lax files: @@ -281,16 +291,16 @@ linters: text: G306 - linters: - gosec - text: "401" + text: '401' - linters: - gosec - text: "402" + text: '402' - linters: - gosec - text: "501" + text: '501' - linters: - gosec - text: "404" + text: '404' - linters: - errorlint text: non-wrapping format verb for fmt.Errorf diff --git a/apps/dashboard/go.mod b/apps/dashboard/go.mod index 2182e0af949..00fe99f0c9c 100644 --- a/apps/dashboard/go.mod +++ b/apps/dashboard/go.mod @@ -9,6 +9,7 @@ require ( github.com/grafana/grafana-app-sdk/logging v0.48.3 github.com/grafana/grafana-plugin-sdk-go v0.284.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e + github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 k8s.io/apimachinery v0.34.2 @@ -57,7 +58,6 @@ require ( github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.7.0 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/jaegertracing/jaeger-idl v0.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect diff --git a/apps/dashboard/pkg/migration/schemaversion/cache.go b/apps/dashboard/pkg/migration/schemaversion/cache.go index f31eaa14e1a..2548c2cc5ba 100644 --- a/apps/dashboard/pkg/migration/schemaversion/cache.go +++ b/apps/dashboard/pkg/migration/schemaversion/cache.go @@ -5,12 +5,11 @@ import ( "sync" "time" - "github.com/grafana/authlib/types" - "github.com/grafana/grafana/pkg/infra/log" "github.com/hashicorp/golang-lru/v2/expirable" - k8srequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/endpoints/request" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/authlib/types" + "github.com/grafana/grafana-app-sdk/logging" ) const defaultCacheSize = 1000 @@ -32,17 +31,15 @@ type cachedProvider[T any] struct { fetch func(context.Context) T cache *expirable.LRU[string, T] // LRU cache: namespace to cache entry inFlight sync.Map // map[string]*sync.Mutex - per-namespace fetch locks - logger log.Logger } // newCachedProvider creates a new cachedProvider. // The fetch function should be able to handle context with different namespaces. // A non-positive size turns LRU mechanism off (cache of unlimited size). // A non-positive cacheTTL disables TTL expiration. -func newCachedProvider[T any](fetch func(context.Context) T, size int, cacheTTL time.Duration, logger log.Logger) *cachedProvider[T] { +func newCachedProvider[T any](fetch func(context.Context) T, size int, cacheTTL time.Duration) *cachedProvider[T] { cacheProvider := &cachedProvider[T]{ - fetch: fetch, - logger: logger, + fetch: fetch, } cacheProvider.cache = expirable.NewLRU(size, func(key string, value T) { cacheProvider.inFlight.Delete(key) @@ -53,14 +50,13 @@ func newCachedProvider[T any](fetch func(context.Context) T, size int, cacheTTL // Get returns the cached value if it's still valid, otherwise calls fetch and caches the result. func (p *cachedProvider[T]) Get(ctx context.Context) T { // Get namespace info from ctx - nsInfo, err := request.NamespaceInfoFrom(ctx, true) - if err != nil { + namespace, ok := request.NamespaceFrom(ctx) + if !ok { // No namespace, fall back to direct fetch call without caching - p.logger.Warn("Unable to get namespace info from context, skipping cache", "error", err) + logging.FromContext(ctx).Warn("Unable to get namespace info from context, skipping cache") return p.fetch(ctx) } - namespace := nsInfo.Value // Fast path: check if cache is still valid if entry, ok := p.cache.Get(namespace); ok { return entry @@ -81,7 +77,7 @@ func (p *cachedProvider[T]) Get(ctx context.Context) T { } // Fetch outside the main lock - only this namespace is blocked - p.logger.Debug("cache miss or expired, fetching new value", "namespace", namespace) + logging.FromContext(ctx).Debug("cache miss or expired, fetching new value", "namespace", namespace) value := p.fetch(ctx) // Update the cache for this namespace @@ -93,12 +89,12 @@ func (p *cachedProvider[T]) Get(ctx context.Context) T { // Preload loads data into the cache for the given namespaces. func (p *cachedProvider[T]) Preload(ctx context.Context, nsInfos []types.NamespaceInfo) { // Build the cache using a context with the namespace - p.logger.Info("preloading cache", "nsInfos", len(nsInfos)) + logging.FromContext(ctx).Info("preloading cache", "nsInfos", len(nsInfos)) startedAt := time.Now() defer func() { - p.logger.Info("finished preloading cache", "nsInfos", len(nsInfos), "elapsed", time.Since(startedAt)) + logging.FromContext(ctx).Info("finished preloading cache", "nsInfos", len(nsInfos), "elapsed", time.Since(startedAt)) }() for _, nsInfo := range nsInfos { - p.cache.Add(nsInfo.Value, p.fetch(k8srequest.WithNamespace(ctx, nsInfo.Value))) + p.cache.Add(nsInfo.Value, p.fetch(request.WithNamespace(ctx, nsInfo.Value))) } } diff --git a/apps/dashboard/pkg/migration/schemaversion/cache_test.go b/apps/dashboard/pkg/migration/schemaversion/cache_test.go index f044ab0f813..081455143e4 100644 --- a/apps/dashboard/pkg/migration/schemaversion/cache_test.go +++ b/apps/dashboard/pkg/migration/schemaversion/cache_test.go @@ -8,11 +8,11 @@ import ( "testing" "time" - authlib "github.com/grafana/authlib/types" - "github.com/grafana/grafana/pkg/infra/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apiserver/pkg/endpoints/request" + + authlib "github.com/grafana/authlib/types" ) // testProvider tracks how many times get() is called @@ -44,7 +44,7 @@ func TestCachedProvider_CacheHit(t *testing.T) { underlying := newTestProvider(datasources) // Test newCachedProvider directly instead of the wrapper - cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute, log.New("test")) + cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute) // Use "default" namespace (org 1) - this is the standard Grafana namespace format ctx := request.WithNamespace(context.Background(), "default") @@ -69,7 +69,7 @@ func TestCachedProvider_NamespaceIsolation(t *testing.T) { } underlying := newTestProvider(datasources) - cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute, log.New("test")) + cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute) // Use "default" (org 1) and "org-2" (org 2) - standard Grafana namespace formats ctx1 := request.WithNamespace(context.Background(), "default") @@ -102,7 +102,7 @@ func TestCachedProvider_NoNamespaceFallback(t *testing.T) { } underlying := newTestProvider(datasources) - cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute, log.New("test")) + cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute) // Context without namespace - should fall back to direct provider call ctx := context.Background() @@ -123,7 +123,7 @@ func TestCachedProvider_ConcurrentAccess(t *testing.T) { } underlying := newTestProvider(datasources) - cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute, log.New("test")) + cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute) // Use "default" namespace (org 1) ctx := request.WithNamespace(context.Background(), "default") @@ -155,7 +155,7 @@ func TestCachedProvider_ConcurrentNamespaces(t *testing.T) { } underlying := newTestProvider(datasources) - cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute, log.New("test")) + cached := newCachedProvider(underlying.get, defaultCacheSize, time.Minute) var wg sync.WaitGroup numOrgs := 10 @@ -198,7 +198,7 @@ func TestCachedProvider_CorrectDataPerNamespace(t *testing.T) { "org-2": {{UID: "org2-ds", Type: "loki", Name: "Org2 DS", Default: true}}, }, } - cached := newCachedProvider(underlying.Index, defaultCacheSize, time.Minute, log.New("test")) + cached := newCachedProvider(underlying.Index, defaultCacheSize, time.Minute) // Use valid namespace formats ctx1 := request.WithNamespace(context.Background(), "default") @@ -228,7 +228,7 @@ func TestCachedProvider_PreloadMultipleNamespaces(t *testing.T) { "org-3": {{UID: "org3-ds", Type: "tempo", Name: "Org3 DS", Default: true}}, }, } - cached := newCachedProvider(underlying.Index, defaultCacheSize, time.Minute, log.New("test")) + cached := newCachedProvider(underlying.Index, defaultCacheSize, time.Minute) // Preload multiple namespaces nsInfos := []authlib.NamespaceInfo{ @@ -346,7 +346,7 @@ func TestCachedProvider_TTLExpiration(t *testing.T) { underlying := newTestProvider(datasources) // Use a very short TTL for testing shortTTL := 50 * time.Millisecond - cached := newCachedProvider(underlying.get, defaultCacheSize, shortTTL, log.New("test")) + cached := newCachedProvider(underlying.get, defaultCacheSize, shortTTL) ctx := request.WithNamespace(context.Background(), "default") @@ -379,7 +379,7 @@ func TestCachedProvider_ParallelNamespacesFetch(t *testing.T) { {UID: "ds1", Type: "prometheus", Name: "Prometheus", Default: true}, }, } - cached := newCachedProvider(provider.get, defaultCacheSize, time.Minute, log.New("test")) + cached := newCachedProvider(provider.get, defaultCacheSize, time.Minute) numNamespaces := 5 var wg sync.WaitGroup @@ -421,7 +421,7 @@ func TestCachedProvider_SameNamespaceSerialFetch(t *testing.T) { {UID: "ds1", Type: "prometheus", Name: "Prometheus", Default: true}, }, } - cached := newCachedProvider(provider.get, defaultCacheSize, time.Minute, log.New("test")) + cached := newCachedProvider(provider.get, defaultCacheSize, time.Minute) numGoroutines := 10 var wg sync.WaitGroup diff --git a/apps/dashboard/pkg/migration/schemaversion/datasource_utils.go b/apps/dashboard/pkg/migration/schemaversion/datasource_utils.go index 9deee29b414..c7215495dc9 100644 --- a/apps/dashboard/pkg/migration/schemaversion/datasource_utils.go +++ b/apps/dashboard/pkg/migration/schemaversion/datasource_utils.go @@ -3,8 +3,6 @@ package schemaversion import ( "context" "time" - - "github.com/grafana/grafana/pkg/infra/log" ) // Shared utility functions for datasource migrations across different schema versions. @@ -36,7 +34,7 @@ func WrapIndexProviderWithCache(provider DataSourceIndexProvider, cacheTTL time. return provider } return &cachedIndexProvider{ - newCachedProvider[*DatasourceIndex](provider.Index, defaultCacheSize, cacheTTL, log.New("schemaversion.dsindexprovider")), + newCachedProvider[*DatasourceIndex](provider.Index, defaultCacheSize, cacheTTL), } } @@ -46,7 +44,7 @@ func WrapLibraryElementProviderWithCache(provider LibraryElementIndexProvider, c return provider } return &cachedLibraryElementProvider{ - newCachedProvider[[]LibraryElementInfo](provider.GetLibraryElementInfo, defaultCacheSize, cacheTTL, log.New("schemaversion.leindexprovider")), + newCachedProvider[[]LibraryElementInfo](provider.GetLibraryElementInfo, defaultCacheSize, cacheTTL), } } From 18837682cc68b33a217f9f8c332cadb2ad40acf8 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:01:03 +0100 Subject: [PATCH 146/297] Scopes: ScopesNavigation preload functionality (#115354) * Add devenv configs * Initial preload functionality * Remove support for expandOnLoad * Add tests * Remove unnecessary go code --- devenv/scopes/scopes-config.yaml | 2 + devenv/scopes/scopes.go | 16 +- .../ScopesDashboardsService.test.ts | 448 ++++++++++++++++++ .../dashboards/ScopesDashboardsService.ts | 33 +- .../app/features/scopes/dashboards/types.ts | 1 + 5 files changed, 492 insertions(+), 8 deletions(-) diff --git a/devenv/scopes/scopes-config.yaml b/devenv/scopes/scopes-config.yaml index d18679f6dcd..ef800415547 100644 --- a/devenv/scopes/scopes-config.yaml +++ b/devenv/scopes/scopes-config.yaml @@ -125,6 +125,7 @@ navigationTree: url: /d/_5rDmaQiz scope: shoe-org subScope: shoes + preLoadSubScopeChildren: true children: - name: shoes-overview title: Overview @@ -141,6 +142,7 @@ navigationTree: url: /d/edediimbjhdz4b scope: shoes subScope: frontend + preLoadSubScopeChildren: true children: - name: frontend-api title: API Metrics diff --git a/devenv/scopes/scopes.go b/devenv/scopes/scopes.go index b252072c398..e3f4de80d21 100644 --- a/devenv/scopes/scopes.go +++ b/devenv/scopes/scopes.go @@ -83,6 +83,7 @@ type NavigationConfig struct { Title string `yaml:"title"` // Display title Groups []string `yaml:"groups"` // Optional groups for categorization DisableSubScopeSelection bool `yaml:"disableSubScopeSelection"` // Makes the subscope not selectable + PreLoadSubScopeChildren bool `yaml:"preLoadSubScopeChildren"` // Preload children of subScope without updating UI } // NavigationTreeNode represents a node in the navigation tree structure @@ -94,6 +95,7 @@ type NavigationTreeNode struct { SubScope string `yaml:"subScope,omitempty"` Groups []string `yaml:"groups,omitempty"` DisableSubScopeSelection bool `yaml:"disableSubScopeSelection,omitempty"` + PreLoadSubScopeChildren bool `yaml:"preLoadSubScopeChildren,omitempty"` // Preload children of subScope without updating UI Children []NavigationTreeNode `yaml:"children,omitempty"` } @@ -318,6 +320,7 @@ func (c *Client) createScopeNavigation(name string, nav NavigationConfig) error URL: nav.URL, Scope: prefixedScope, DisableSubScopeSelection: nav.DisableSubScopeSelection, + PreLoadSubScopeChildren: nav.PreLoadSubScopeChildren, } if nav.SubScope != "" { @@ -353,14 +356,14 @@ func (c *Client) createScopeNavigation(name string, nav NavigationConfig) error return err } + // Get the created resource to retrieve its resourceVersion for status update + createdNav, err := c.getScopeNavigation(prefixedName) + if err != nil { + return fmt.Errorf("failed to get created navigation: %w", err) + } + // Update status in a second request (status is a subresource) if nav.Title != "" || len(nav.Groups) > 0 { - // Get the created resource to retrieve its resourceVersion and existing spec - createdNav, err := c.getScopeNavigation(prefixedName) - if err != nil { - return fmt.Errorf("failed to get created navigation: %w", err) - } - statusResource := v0alpha1.ScopeNavigation{ TypeMeta: metav1.TypeMeta{ APIVersion: apiVersion, @@ -411,6 +414,7 @@ func treeToNavigations(node NavigationTreeNode, parentPath []string, dashboardCo Scope: node.Scope, Title: node.Title, DisableSubScopeSelection: node.DisableSubScopeSelection, + PreLoadSubScopeChildren: node.PreLoadSubScopeChildren, } if node.SubScope != "" { nav.SubScope = node.SubScope diff --git a/public/app/features/scopes/dashboards/ScopesDashboardsService.test.ts b/public/app/features/scopes/dashboards/ScopesDashboardsService.test.ts index 082c8c790cc..2c5e4b28fc6 100644 --- a/public/app/features/scopes/dashboards/ScopesDashboardsService.test.ts +++ b/public/app/features/scopes/dashboards/ScopesDashboardsService.test.ts @@ -888,6 +888,454 @@ describe('ScopesDashboardsService', () => { }); }); + describe('preLoadSubScopeChildren', () => { + beforeEach(() => { + config.featureToggles.useScopesNavigationEndpoint = true; + (locationService.getLocation as jest.Mock).mockReturnValue({ pathname: '/' } as Location); + }); + + afterEach(() => { + config.featureToggles.useScopesNavigationEndpoint = false; + }); + + it('should set preLoadSubScopeChildren on folder when navigation has it set to true', async () => { + const mockNavigations: ScopeNavigation[] = [ + { + spec: { + url: '/d/dashboard1', + scope: 'scope1', + subScope: 'subScope1', + preLoadSubScopeChildren: true, + }, + status: { + title: 'Test Navigation', + }, + metadata: { + name: 'nav1', + }, + }, + ]; + + mockApiClient.fetchScopeNavigations.mockResolvedValue(mockNavigations); + await service.fetchDashboards(['scope1']); + + const folderKey = Object.keys(service.state.folders[''].folders).find((key) => key.includes('subScope1')); + expect(folderKey).toBeDefined(); + + if (folderKey) { + const folder = service.state.folders[''].folders[folderKey]; + expect(folder.preLoadSubScopeChildren).toBe(true); + expect(folder.subScopeName).toBe('subScope1'); + } + }); + + it('should set preLoadSubScopeChildren to false when navigation has it set to false', async () => { + const mockNavigations: ScopeNavigation[] = [ + { + spec: { + url: '/d/dashboard1', + scope: 'scope1', + subScope: 'subScope1', + preLoadSubScopeChildren: false, + }, + status: { + title: 'Test Navigation', + }, + metadata: { + name: 'nav1', + }, + }, + ]; + + mockApiClient.fetchScopeNavigations.mockResolvedValue(mockNavigations); + await service.fetchDashboards(['scope1']); + + const folderKey = Object.keys(service.state.folders[''].folders).find((key) => key.includes('subScope1')); + expect(folderKey).toBeDefined(); + + if (folderKey) { + const folder = service.state.folders[''].folders[folderKey]; + expect(folder.preLoadSubScopeChildren).toBe(false); + } + }); + + it('should set preLoadSubScopeChildren to undefined when navigation does not have it', async () => { + const mockNavigations: ScopeNavigation[] = [ + { + spec: { + url: '/d/dashboard1', + scope: 'scope1', + subScope: 'subScope1', + }, + status: { + title: 'Test Navigation', + }, + metadata: { + name: 'nav1', + }, + }, + ]; + + mockApiClient.fetchScopeNavigations.mockResolvedValue(mockNavigations); + await service.fetchDashboards(['scope1']); + + const folderKey = Object.keys(service.state.folders[''].folders).find((key) => key.includes('subScope1')); + expect(folderKey).toBeDefined(); + + if (folderKey) { + const folder = service.state.folders[''].folders[folderKey]; + expect(folder.preLoadSubScopeChildren).toBeUndefined(); + } + }); + + it('should automatically fetch subScope items for folders with preLoadSubScopeChildren set to true', async () => { + const mockNavigations: ScopeNavigation[] = [ + { + spec: { + url: '/d/dashboard1', + scope: 'scope1', + subScope: 'mimir', + preLoadSubScopeChildren: true, + }, + status: { + title: 'Mimir Dashboards', + }, + metadata: { + name: 'nav1', + }, + }, + ]; + + // Mock items returned when fetching 'mimir' subScope + const mimirItems: ScopeNavigation[] = [ + { + metadata: { name: 'mimir-item-1' }, + spec: { + scope: 'mimir', + url: '/d/mimir-dashboard-1', + }, + status: { + title: 'Mimir Dashboard 1', + groups: ['General'], + }, + }, + ]; + + mockApiClient.fetchScopeNavigations.mockImplementation((scopeNames: string[]) => { + if (scopeNames.includes('scope1')) { + return Promise.resolve(mockNavigations); + } + if (scopeNames.includes('mimir')) { + return Promise.resolve(mimirItems); + } + return Promise.resolve([]); + }); + + await service.fetchDashboards(['scope1']); + + // Wait for the preload to complete + await new Promise((resolve) => setTimeout(resolve, 0)); + + // Verify that fetchScopeNavigations was called for the subScope + expect(mockApiClient.fetchScopeNavigations).toHaveBeenCalledWith(['mimir']); + + // Verify the folder now has content from the preloaded items + const folderKey = Object.keys(service.state.folders[''].folders).find((key) => key.includes('mimir')); + expect(folderKey).toBeDefined(); + + if (folderKey) { + const folder = service.state.folders[''].folders[folderKey]; + // The preloaded items should be in the folder + expect(folder.folders['General']).toBeDefined(); + expect(folder.folders['General'].suggestedNavigations['/d/mimir-dashboard-1']).toBeDefined(); + } + }); + + it('should not fetch subScope items for folders without preLoadSubScopeChildren', async () => { + const mockNavigations: ScopeNavigation[] = [ + { + spec: { + url: '/d/dashboard1', + scope: 'scope1', + subScope: 'mimir', + // preLoadSubScopeChildren is not set + }, + status: { + title: 'Mimir Dashboards', + }, + metadata: { + name: 'nav1', + }, + }, + ]; + + mockApiClient.fetchScopeNavigations.mockResolvedValue(mockNavigations); + await service.fetchDashboards(['scope1']); + + // Wait to ensure no additional fetch happens + await new Promise((resolve) => setTimeout(resolve, 0)); + + // Verify that fetchScopeNavigations was only called once (for the initial fetch) + expect(mockApiClient.fetchScopeNavigations).toHaveBeenCalledTimes(1); + expect(mockApiClient.fetchScopeNavigations).toHaveBeenCalledWith(['scope1']); + }); + + it('should recursively preload nested folders with preLoadSubScopeChildren', async () => { + const mockNavigations: ScopeNavigation[] = [ + { + spec: { + url: '/d/dashboard1', + scope: 'scope1', + subScope: 'level1', + preLoadSubScopeChildren: true, + }, + status: { + title: 'Level 1 Folder', + }, + metadata: { + name: 'nav1', + }, + }, + ]; + + // Level 1 items include a folder with preLoadSubScopeChildren + const level1Items: ScopeNavigation[] = [ + { + metadata: { name: 'level2-nav' }, + spec: { + scope: 'level1', + subScope: 'level2', + url: '/d/level2-dashboard', + preLoadSubScopeChildren: true, + }, + status: { + title: 'Level 2 Folder', + }, + }, + ]; + + // Level 2 items + const level2Items: ScopeNavigation[] = [ + { + metadata: { name: 'level2-item-1' }, + spec: { + scope: 'level2', + url: '/d/level2-dashboard-1', + }, + status: { + title: 'Level 2 Dashboard 1', + groups: ['Deep'], + }, + }, + ]; + + mockApiClient.fetchScopeNavigations.mockImplementation((scopeNames: string[]) => { + if (scopeNames.includes('scope1')) { + return Promise.resolve(mockNavigations); + } + if (scopeNames.includes('level1')) { + return Promise.resolve(level1Items); + } + if (scopeNames.includes('level2')) { + return Promise.resolve(level2Items); + } + return Promise.resolve([]); + }); + + await service.fetchDashboards(['scope1']); + + // Wait for all preloads to complete (need to wait for both levels) + await new Promise((resolve) => setTimeout(resolve, 10)); + await new Promise((resolve) => setTimeout(resolve, 10)); + + // Verify all levels were fetched + expect(mockApiClient.fetchScopeNavigations).toHaveBeenCalledWith(['level1']); + expect(mockApiClient.fetchScopeNavigations).toHaveBeenCalledWith(['level2']); + }); + + it('should handle multiple folders with preLoadSubScopeChildren', async () => { + const mockNavigations: ScopeNavigation[] = [ + { + spec: { + url: '/d/dashboard1', + scope: 'scope1', + subScope: 'mimir', + preLoadSubScopeChildren: true, + }, + status: { + title: 'Mimir Dashboards', + }, + metadata: { + name: 'nav1', + }, + }, + { + spec: { + url: '/d/dashboard2', + scope: 'scope1', + subScope: 'loki', + preLoadSubScopeChildren: true, + }, + status: { + title: 'Loki Dashboards', + }, + metadata: { + name: 'nav2', + }, + }, + ]; + + const mimirItems: ScopeNavigation[] = [ + { + metadata: { name: 'mimir-item-1' }, + spec: { scope: 'mimir', url: '/d/mimir-dashboard-1' }, + status: { title: 'Mimir Dashboard 1', groups: ['General'] }, + }, + ]; + + const lokiItems: ScopeNavigation[] = [ + { + metadata: { name: 'loki-item-1' }, + spec: { scope: 'loki', url: '/d/loki-dashboard-1' }, + status: { title: 'Loki Dashboard 1', groups: ['General'] }, + }, + ]; + + mockApiClient.fetchScopeNavigations.mockImplementation((scopeNames: string[]) => { + if (scopeNames.includes('scope1')) { + return Promise.resolve(mockNavigations); + } + if (scopeNames.includes('mimir')) { + return Promise.resolve(mimirItems); + } + if (scopeNames.includes('loki')) { + return Promise.resolve(lokiItems); + } + return Promise.resolve([]); + }); + + await service.fetchDashboards(['scope1']); + + // Wait for preloads to complete + await new Promise((resolve) => setTimeout(resolve, 0)); + + // Verify both subScopes were fetched + expect(mockApiClient.fetchScopeNavigations).toHaveBeenCalledWith(['mimir']); + expect(mockApiClient.fetchScopeNavigations).toHaveBeenCalledWith(['loki']); + }); + + it('should handle preload errors gracefully', async () => { + const mockNavigations: ScopeNavigation[] = [ + { + spec: { + url: '/d/dashboard1', + scope: 'scope1', + subScope: 'failing-scope', + preLoadSubScopeChildren: true, + }, + status: { + title: 'Failing Folder', + }, + metadata: { + name: 'nav1', + }, + }, + ]; + + mockApiClient.fetchScopeNavigations.mockImplementation((scopeNames: string[]) => { + if (scopeNames.includes('scope1')) { + return Promise.resolve(mockNavigations); + } + if (scopeNames.includes('failing-scope')) { + return Promise.reject(new Error('Network error')); + } + return Promise.resolve([]); + }); + + // Should not throw + await service.fetchDashboards(['scope1']); + + // Wait for preload to attempt + await new Promise((resolve) => setTimeout(resolve, 0)); + + // Verify the folder was still created (even though preload failed) + const folderKey = Object.keys(service.state.folders[''].folders).find((key) => key.includes('failing-scope')); + expect(folderKey).toBeDefined(); + }); + + it('should preload children after fetching subScope items when parent folder has items with preLoadSubScopeChildren', async () => { + const mockNavigations: ScopeNavigation[] = [ + { + spec: { + url: '/d/dashboard1', + scope: 'scope1', + subScope: 'parent', + preLoadSubScopeChildren: true, + }, + status: { + title: 'Parent Folder', + }, + metadata: { + name: 'nav1', + }, + }, + ]; + + // Parent items include a child with preLoadSubScopeChildren + const parentItems: ScopeNavigation[] = [ + { + metadata: { name: 'child-nav' }, + spec: { + scope: 'parent', + subScope: 'child', + url: '/d/child-dashboard', + preLoadSubScopeChildren: true, + }, + status: { + title: 'Child Folder', + }, + }, + ]; + + const childItems: ScopeNavigation[] = [ + { + metadata: { name: 'child-item-1' }, + spec: { + scope: 'child', + url: '/d/child-dashboard-1', + }, + status: { + title: 'Child Dashboard 1', + groups: ['Nested'], + }, + }, + ]; + + mockApiClient.fetchScopeNavigations.mockImplementation((scopeNames: string[]) => { + if (scopeNames.includes('scope1')) { + return Promise.resolve(mockNavigations); + } + if (scopeNames.includes('parent')) { + return Promise.resolve(parentItems); + } + if (scopeNames.includes('child')) { + return Promise.resolve(childItems); + } + return Promise.resolve([]); + }); + + await service.fetchDashboards(['scope1']); + + // Wait for cascading preloads + await new Promise((resolve) => setTimeout(resolve, 20)); + + // Verify the chain of preloads occurred + expect(mockApiClient.fetchScopeNavigations).toHaveBeenCalledWith(['scope1']); + expect(mockApiClient.fetchScopeNavigations).toHaveBeenCalledWith(['parent']); + expect(mockApiClient.fetchScopeNavigations).toHaveBeenCalledWith(['child']); + }); + }); + describe('disableSubScopeSelection', () => { it('should set disableSubScopeSelection on folder when navigation has it set to true', async () => { const mockNavigations: ScopeNavigation[] = [ diff --git a/public/app/features/scopes/dashboards/ScopesDashboardsService.ts b/public/app/features/scopes/dashboards/ScopesDashboardsService.ts index 4d6c686280d..a0def1b4fd5 100644 --- a/public/app/features/scopes/dashboards/ScopesDashboardsService.ts +++ b/public/app/features/scopes/dashboards/ScopesDashboardsService.ts @@ -253,9 +253,14 @@ export class ScopesDashboardsService extends ScopesServiceBase 0, }); + + // Preload children for folders with preLoadSubScopeChildren set + this.preloadSubScopeChildren(folders[''].folders, ['']); + } + }; + + /** + * Preloads children for folders that have preLoadSubScopeChildren set to true. + * This fetches the subScope items immediately when the navigation is first loaded, + * or when a parent subScope folder is fetched. + * @param foldersToCheck - The folders to check for preLoadSubScopeChildren + * @param basePath - The path to prepend when building the full path for each folder + */ + private preloadSubScopeChildren = (foldersToCheck: SuggestedNavigationsFoldersMap, basePath: string[]) => { + for (const [folderKey, folder] of Object.entries(foldersToCheck)) { + if (folder.preLoadSubScopeChildren && folder.subScopeName) { + const path = [...basePath, folderKey]; + this.fetchSubScopeItems(path, folder.subScopeName); + } } }; @@ -391,6 +415,10 @@ export class ScopesDashboardsService extends ScopesServiceBase; From 195bf681d14c379cd113a87b71456e7df9581e2f Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Tue, 16 Dec 2025 13:34:08 +0100 Subject: [PATCH 147/297] V2: Ensure refIds for queries (#115404) Ensure refIds for queries --- .../layoutSerializers/utils.test.ts | 75 ++++++++++++++++++- .../serialization/layoutSerializers/utils.ts | 21 +++++- 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts index 328dc680e6e..ad644f92294 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.test.ts @@ -1,6 +1,6 @@ import { defaultDataQueryKind, PanelQueryKind } from '@grafana/schema/dist/esm/schema/dashboard/v2'; -import { getRuntimePanelDataSource } from './utils'; +import { ensureUniqueRefIds, getRuntimePanelDataSource } from './utils'; describe('getRuntimePanelDataSource', () => { it('should return uid and type when explicit datasource UID is provided', () => { @@ -140,3 +140,76 @@ describe('getRuntimePanelDataSource', () => { expect(result).toBeUndefined(); }); }); + +describe('ensureUniqueRefIds', () => { + const createQuery = (refId: string): PanelQueryKind => ({ + kind: 'PanelQuery', + spec: { + refId, + hidden: false, + query: { + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'prometheus', + spec: {}, + }, + }, + }); + + it('should assign unique refIds to queries without refIds', () => { + const queries: PanelQueryKind[] = [createQuery(''), createQuery(''), createQuery('')]; + + const result = ensureUniqueRefIds(queries); + + expect(result[0].spec.refId).toBe('A'); + expect(result[1].spec.refId).toBe('B'); + expect(result[2].spec.refId).toBe('C'); + }); + + it('should preserve existing refIds and fill gaps', () => { + const queries: PanelQueryKind[] = [createQuery('A'), createQuery(''), createQuery('D'), createQuery('')]; + + const result = ensureUniqueRefIds(queries); + + expect(result[0].spec.refId).toBe('A'); + expect(result[1].spec.refId).toBe('B'); + expect(result[2].spec.refId).toBe('D'); + expect(result[3].spec.refId).toBe('C'); + }); + + it('should handle all queries having existing refIds', () => { + const queries: PanelQueryKind[] = [createQuery('A'), createQuery('B'), createQuery('C')]; + + const result = ensureUniqueRefIds(queries); + + expect(result[0].spec.refId).toBe('A'); + expect(result[1].spec.refId).toBe('B'); + expect(result[2].spec.refId).toBe('C'); + }); + + it('should only modify queries without refIds', () => { + const queries: PanelQueryKind[] = [createQuery('A'), createQuery(''), createQuery('C')]; + + const result = ensureUniqueRefIds(queries); + + // Existing refIds should be preserved + expect(result[0].spec.refId).toBe('A'); + expect(result[2].spec.refId).toBe('C'); + // Missing refId should be assigned + expect(result[1].spec.refId).toBe('B'); + }); + + it('should handle empty array', () => { + const result = ensureUniqueRefIds([]); + + expect(result).toEqual([]); + }); + + it('should handle single query without refId', () => { + const queries: PanelQueryKind[] = [createQuery('')]; + + const result = ensureUniqueRefIds(queries); + + expect(result[0].spec.refId).toBe('A'); + }); +}); diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts index fc634339607..6595268025d 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts @@ -1,3 +1,4 @@ +import { getNextRefId } from '@grafana/data'; import { config } from '@grafana/runtime'; import { SceneDataProvider, @@ -164,12 +165,15 @@ export function createPanelDataProvider(panelKind: PanelKind): SceneDataProvider return undefined; } + // Ensure all queries have unique refIds before converting to scene queries + const queriesWithUniqueRefIds = ensureUniqueRefIds(targets); + let dataProvider: SceneDataProvider | undefined = undefined; const datasource = getPanelDataSource(panelKind); dataProvider = new SceneQueryRunner({ datasource, - queries: targets.map(panelQueryKindToSceneQuery), + queries: queriesWithUniqueRefIds.map(panelQueryKindToSceneQuery), maxDataPoints: panel.data.spec.queryOptions.maxDataPoints ?? undefined, maxDataPointsFromWidth: true, cacheTimeout: panel.data.spec.queryOptions.cacheTimeout, @@ -341,6 +345,21 @@ export function getDataSourceForQuery(querySpecDS: DataSourceRef | undefined | n }; } +export function ensureUniqueRefIds(queries: PanelQueryKind[]): PanelQueryKind[] { + // Adapter to make PanelQueryKind[] work with getNextRefId (which expects { refId }[]) + const refIdAdapter = queries.map((q) => ({ refId: q.spec.refId })); + + for (let i = 0; i < queries.length; i++) { + if (!queries[i].spec.refId) { + const newRefId = getNextRefId(refIdAdapter); + queries[i] = { ...queries[i], spec: { ...queries[i].spec, refId: newRefId } }; + refIdAdapter[i] = { refId: newRefId }; + } + } + + return queries; +} + function panelQueryKindToSceneQuery(query: PanelQueryKind): SceneDataQuery { // Add datasource to match Go backend V2→V1 conversion: // - If explicit UID (datasource.name) exists → add { uid, type } From c7c1dd4ead5e0c466788b4dbe75e927e66eaefd5 Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Tue, 16 Dec 2025 13:47:26 +0100 Subject: [PATCH 148/297] V2 Schema: Fix panel Y position when converting from tabs to legacy rows in V2 -> v1 conversion. (#115373) * fix row y conversion * fix only for tabs * add tests examples --- .../v2beta1.tab-with-multiple-panels.json | 715 ++++++++++++++++++ ...ta1.tab-with-multiple-panels.v0alpha1.json | 511 +++++++++++++ ...eta1.tab-with-multiple-panels.v1beta1.json | 511 +++++++++++++ ...ta1.tab-with-multiple-panels.v2alpha1.json | 683 +++++++++++++++++ .../conversion/v2alpha1_to_v1beta1.go | 11 +- 5 files changed, 2428 insertions(+), 3 deletions(-) create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/input/v2beta1.tab-with-multiple-panels.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tab-with-multiple-panels.v0alpha1.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tab-with-multiple-panels.v1beta1.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tab-with-multiple-panels.v2alpha1.json diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/v2beta1.tab-with-multiple-panels.json b/apps/dashboard/pkg/migration/conversion/testdata/input/v2beta1.tab-with-multiple-panels.json new file mode 100644 index 00000000000..c2bb49cc5cb --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/input/v2beta1.tab-with-multiple-panels.json @@ -0,0 +1,715 @@ +{ +"kind": "DashboardWithAccessInfo", +"apiVersion": "dashboard.grafana.app/v2beta1", +"metadata": { + "name": "adt885j", + "namespace": "default", + "uid": "yTWet6JgBjlRIWnqRE9ZOmUycfT0tEkr2mljaln1GWIX", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-12-16T10:44:31Z", + "labels": { + "grafana.app/deprecatedInternalID": "2409" + }, + "annotations": { + "grafana.app/createdBy": "user:u000000001", + "grafana.app/updatedBy": "user:u000000001", + "grafana.app/updatedTimestamp": "2025-12-16T10:51:14Z" + } +}, +"spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true + } + } + ], + "cursorSync": "Off", + "description": "", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel1", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "datasource": { + "name": "PD8C576611E62080A" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "12.4.0-pre", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel2", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "12.4.0-pre", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel3", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "12.4.0-pre", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel4", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "12.4.0-pre", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel5", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "", + "version": "v0", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "12.4.0-pre", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "TabsLayout", + "spec": { + "tabs": [ + { + "kind": "TabsLayoutTab", + "spec": { + "title": "Tab1", + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 7, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 7, + "y": 0, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 15, + "y": 0, + "width": 9, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 8, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 8, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "browser", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Dashboard with tabs", + "variables": [] +}, +"status": {}, +"access": { + "slug": "dashboard-with-tabs", + "url": "/d/adt885j/dashboard-with-tabs", + "isPublic": false, + "canSave": true, + "canEdit": true, + "canAdmin": true, + "canStar": true, + "canDelete": true, + "annotationsPermissions": { + "dashboard": { + "canAdd": true, + "canEdit": true, + "canDelete": true + }, + "organization": { + "canAdd": true, + "canEdit": true, + "canDelete": true + } + } +} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tab-with-multiple-panels.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tab-with-multiple-panels.v0alpha1.json new file mode 100644 index 00000000000..0357c22536a --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tab-with-multiple-panels.v0alpha1.json @@ -0,0 +1,511 @@ +{ + "kind": "DashboardWithAccessInfo", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "adt885j", + "namespace": "default", + "uid": "yTWet6JgBjlRIWnqRE9ZOmUycfT0tEkr2mljaln1GWIX", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-12-16T10:44:31Z", + "labels": { + "grafana.app/deprecatedInternalID": "2409" + }, + "annotations": { + "grafana.app/createdBy": "user:u000000001", + "grafana.app/updatedBy": "user:u000000001", + "grafana.app/updatedTimestamp": "2025-12-16T10:51:14Z" + } + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": -1, + "panels": [], + "title": "Tab1", + "type": "row" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 8, + "w": 7, + "x": 0, + "y": 1 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], + "title": "Panel1", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 7, + "y": 1 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-pre", + "targets": [ + { + "refId": "A" + } + ], + "title": "Panel2", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 15, + "y": 1 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-pre", + "targets": [ + { + "refId": "A" + } + ], + "title": "Panel3", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-pre", + "targets": [ + { + "refId": "A" + } + ], + "title": "Panel4", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-pre", + "targets": [ + { + "refId": "A" + } + ], + "title": "Panel5", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "browser", + "title": "Dashboard with tabs" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tab-with-multiple-panels.v1beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tab-with-multiple-panels.v1beta1.json new file mode 100644 index 00000000000..7a9ea77ba65 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tab-with-multiple-panels.v1beta1.json @@ -0,0 +1,511 @@ +{ + "kind": "DashboardWithAccessInfo", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "adt885j", + "namespace": "default", + "uid": "yTWet6JgBjlRIWnqRE9ZOmUycfT0tEkr2mljaln1GWIX", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-12-16T10:44:31Z", + "labels": { + "grafana.app/deprecatedInternalID": "2409" + }, + "annotations": { + "grafana.app/createdBy": "user:u000000001", + "grafana.app/updatedBy": "user:u000000001", + "grafana.app/updatedTimestamp": "2025-12-16T10:51:14Z" + } + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": -1, + "panels": [], + "title": "Tab1", + "type": "row" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 8, + "w": 7, + "x": 0, + "y": 1 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A" + } + ], + "title": "Panel1", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 7, + "y": 1 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-pre", + "targets": [ + { + "refId": "A" + } + ], + "title": "Panel2", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 15, + "y": 1 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-pre", + "targets": [ + { + "refId": "A" + } + ], + "title": "Panel3", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-pre", + "targets": [ + { + "refId": "A" + } + ], + "title": "Panel4", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + } + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-pre", + "targets": [ + { + "refId": "A" + } + ], + "title": "Panel5", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 42, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "browser", + "title": "Dashboard with tabs" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tab-with-multiple-panels.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tab-with-multiple-panels.v2alpha1.json new file mode 100644 index 00000000000..b7739508c2d --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v2beta1.tab-with-multiple-panels.v2alpha1.json @@ -0,0 +1,683 @@ +{ + "kind": "DashboardWithAccessInfo", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "adt885j", + "namespace": "default", + "uid": "yTWet6JgBjlRIWnqRE9ZOmUycfT0tEkr2mljaln1GWIX", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-12-16T10:44:31Z", + "labels": { + "grafana.app/deprecatedInternalID": "2409" + }, + "annotations": { + "grafana.app/createdBy": "user:u000000001", + "grafana.app/updatedBy": "user:u000000001", + "grafana.app/updatedTimestamp": "2025-12-16T10:51:14Z" + } + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "grafana", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true + } + } + ], + "cursorSync": "Off", + "description": "", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel1", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": {} + }, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "PD8C576611E62080A" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "12.4.0-pre", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel2", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "12.4.0-pre", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-3": { + "kind": "Panel", + "spec": { + "id": 3, + "title": "Panel3", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "12.4.0-pre", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Panel4", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "12.4.0-pre", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + }, + "panel-5": { + "kind": "Panel", + "spec": { + "id": 5, + "title": "Panel5", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "12.4.0-pre", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "TabsLayout", + "spec": { + "tabs": [ + { + "kind": "TabsLayoutTab", + "spec": { + "title": "Tab1", + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 7, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 7, + "y": 0, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 15, + "y": 0, + "width": 9, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-3" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 8, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 8, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-5" + } + } + } + ] + } + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "browser", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Dashboard with tabs", + "variables": [] + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v2beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v1beta1.go b/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v1beta1.go index 73fd97b2089..438aad5ead7 100644 --- a/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v1beta1.go +++ b/apps/dashboard/pkg/migration/conversion/v2alpha1_to_v1beta1.go @@ -495,6 +495,9 @@ func processTabItem(elements map[string]dashv2alpha1.DashboardElement, tab *dash currentY = getMaxYFromPanels(nestedPanels, currentY) } else if tab.Spec.Layout.GridLayoutKind != nil { // GridLayout inside tab + baseY := currentY + maxY := currentY + for _, item := range tab.Spec.Layout.GridLayoutKind.Spec.Items { element, ok := elements[item.Spec.Element.Name] if !ok { @@ -502,7 +505,7 @@ func processTabItem(elements map[string]dashv2alpha1.DashboardElement, tab *dash } adjustedItem := item - adjustedItem.Spec.Y = item.Spec.Y + currentY + adjustedItem.Spec.Y = item.Spec.Y + baseY panel, err := convertPanelFromElement(&element, &adjustedItem) if err != nil { @@ -511,10 +514,12 @@ func processTabItem(elements map[string]dashv2alpha1.DashboardElement, tab *dash panels = append(panels, panel) panelEndY := adjustedItem.Spec.Y + item.Spec.Height - if panelEndY > currentY { - currentY = panelEndY + if panelEndY > maxY { + maxY = panelEndY } } + + currentY = maxY } else if tab.Spec.Layout.AutoGridLayoutKind != nil { // AutoGridLayout inside tab - convert with Y offset autoGridPanels, err := convertAutoGridLayoutToPanelsWithOffset(elements, tab.Spec.Layout.AutoGridLayoutKind, currentY) From c0295d06a3ddfdc54567ab027e57e672d4f1e84e Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Tue, 16 Dec 2025 14:13:50 +0100 Subject: [PATCH 149/297] Alerting: Add rule_matcher filter to Prometheus rules API (#115297) **What is this feature?** Add `rule_matcher` filter to the Prometheus-compatible list rules API: `/api/prometheus/grafana/api/v1/rules`. It allows to filter rules by static labels (not by alert instance labels). **Special notes:** - Equality (`=`) and inequality (`!=`) matchers are pushed down to the database. Regex matchers (`=~`, `!~`) are applied in-memory at the API layer. - SQLite: Uses GLOB pattern matching - MySQL / PostgreSQL: Use JSON functions to compare label values --------- Co-authored-by: Konrad Lalik --- .../ngalert/api/api_prometheus_test.go | 183 +++++++++++++++++ .../ngalert/api/prometheus/api_prometheus.go | 143 ++++++++++---- .../ngalert/api/tooling/definitions/prom.go | 6 + pkg/services/ngalert/api/tooling/post.json | 9 + pkg/services/ngalert/api/tooling/spec.json | 9 + pkg/services/ngalert/models/alert_rule.go | 5 + pkg/services/ngalert/store/alert_rule.go | 39 ++++ .../ngalert/store/alert_rule_labels.go | 51 +++++ .../ngalert/store/alert_rule_labels_test.go | 136 +++++++++++++ pkg/services/ngalert/store/alert_rule_test.go | 152 ++++++++++++++ pkg/services/ngalert/store/json.go | 101 ++++++++++ pkg/services/ngalert/store/json_test.go | 185 ++++++++++++++++++ pkg/services/ngalert/tests/fakes/rules.go | 15 ++ pkg/tests/api/alerting/api_prometheus_test.go | 69 ++++++- .../alerting/unified/api/prometheusApi.ts | 3 + .../rule-list/hooks/grafanaFilter.test.ts | 67 ++++--- .../unified/rule-list/hooks/grafanaFilter.ts | 30 ++- .../rule-list/paginationLimits.test.ts | 5 +- 18 files changed, 1137 insertions(+), 71 deletions(-) create mode 100644 pkg/services/ngalert/store/alert_rule_labels.go create mode 100644 pkg/services/ngalert/store/alert_rule_labels_test.go create mode 100644 pkg/services/ngalert/store/json.go create mode 100644 pkg/services/ngalert/store/json_test.go diff --git a/pkg/services/ngalert/api/api_prometheus_test.go b/pkg/services/ngalert/api/api_prometheus_test.go index 25acf56f7d3..71e25d4c963 100644 --- a/pkg/services/ngalert/api/api_prometheus_test.go +++ b/pkg/services/ngalert/api/api_prometheus_test.go @@ -2886,6 +2886,189 @@ func TestRouteGetRuleStatuses(t *testing.T) { }) } }) + + t.Run("with rule_matcher filter", func(t *testing.T) { + fakeStore, fakeAIM, api := setupAPI(t) + + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithUID("rule1"), gen.WithLabels(map[string]string{"team": "alerting", "severity": "critical"}), gen.WithNoNotificationSettings()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithUID("rule2"), gen.WithLabels(map[string]string{"team": "Alerting", "severity": "warning"}), gen.WithNoNotificationSettings()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithUID("rule3"), gen.WithLabels(map[string]string{"team": "platform", "severity": "critical"}), gen.WithNoNotificationSettings()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithUID("rule4"), gen.WithLabels(map[string]string{"env": "production"}), gen.WithNoNotificationSettings()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithUID("rule_special"), gen.WithLabels(map[string]string{"key": `value"with"quotes`}), gen.WithNoNotificationSettings()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithUID("rule_empty"), gen.WithLabels(map[string]string{"empty": ""}), gen.WithNoNotificationSettings()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithUID("rule_nonempty"), gen.WithLabels(map[string]string{"empty": "nonempty"}), gen.WithNoNotificationSettings()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithUID("rule_multiline"), gen.WithLabels(map[string]string{"description": "line1\nline2\\end\"quote"}), gen.WithNoNotificationSettings()) + + testCases := []struct { + name string + matchers []string + expectedUIDs []string + }{ + { + name: "equality matcher filters by team=alerting", + matchers: []string{`{"name":"team","value":"alerting","isRegex":false,"isEqual":true}`}, + expectedUIDs: []string{"rule1"}, + }, + { + name: "inequality matcher filters severity!=warning", + matchers: []string{`{"name":"severity","value":"warning","isRegex":false,"isEqual":false}`}, + expectedUIDs: []string{"rule1", "rule3", "rule4", "rule_special", "rule_empty", "rule_nonempty", "rule_multiline"}, + }, + { + name: "regex matcher filters team=~plat.*", + matchers: []string{`{"name":"team","value":"plat.*","isRegex":true,"isEqual":true}`}, + expectedUIDs: []string{"rule3"}, + }, + { + name: "not-regex matcher filters severity!~warn.*", + matchers: []string{`{"name":"severity","value":"warn.*","isRegex":true,"isEqual":false}`}, + expectedUIDs: []string{"rule1", "rule3", "rule4", "rule_special", "rule_empty", "rule_nonempty", "rule_multiline"}, + }, + { + name: "multiple matchers are ANDed", + matchers: []string{ + `{"name":"team","value":"alerting","isRegex":false,"isEqual":true}`, + `{"name":"severity","value":"critical","isRegex":false,"isEqual":true}`, + }, + expectedUIDs: []string{"rule1"}, + }, + { + name: "matcher with non-existent label returns no rules", + matchers: []string{`{"name":"nonexistent","value":"value","isRegex":false,"isEqual":true}`}, + expectedUIDs: []string{}, + }, + { + name: "equality matcher is case-sensitive", + matchers: []string{`{"name":"team","value":"Alerting","isRegex":false,"isEqual":true}`}, + expectedUIDs: []string{"rule2"}, + }, + { + name: "quotes in label value are handled correctly", + matchers: []string{`{"name":"key","value":"value\"with\"quotes","isRegex":false,"isEqual":true}`}, + expectedUIDs: []string{"rule_special"}, + }, + { + name: "no matchers returns all rules", + matchers: []string{}, + expectedUIDs: []string{"rule1", "rule2", "rule3", "rule4", "rule_special", "rule_empty", "rule_nonempty", "rule_multiline"}, + }, + { + name: "empty string value matches correctly", + matchers: []string{`{"name":"empty","value":"","isRegex":false,"isEqual":true}`}, + expectedUIDs: []string{"rule1", "rule2", "rule3", "rule4", "rule_special", "rule_empty", "rule_multiline"}, + }, + { + name: "special characters in label value are handled correctly", + matchers: []string{`{"name":"description","value":"line1\nline2\\end\"quote","isRegex":false,"isEqual":true}`}, + expectedUIDs: []string{"rule_multiline"}, + }, + { + name: "inequality matcher on non-existent label matches all rules", + matchers: []string{`{"name":"nonexistent","value":"value","isRegex":false,"isEqual":false}`}, + expectedUIDs: []string{"rule1", "rule2", "rule3", "rule4", "rule_special", "rule_empty", "rule_nonempty", "rule_multiline"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + reqURL := "/api/v1/rules" + for i, matcher := range tc.matchers { + if i == 0 { + reqURL += "?rule_matcher=" + url.QueryEscape(matcher) + } else { + reqURL += "&rule_matcher=" + url.QueryEscape(matcher) + } + } + + req, err := http.NewRequest("GET", reqURL, nil) + require.NoError(t, err) + ctx := &contextmodel.ReqContext{ + Context: &web.Context{Req: req}, + SignedInUser: &user.SignedInUser{OrgID: orgID, Permissions: queryPermissions}, + } + + resp := api.RouteGetRuleStatuses(ctx) + require.Equal(t, http.StatusOK, resp.Status()) + + var res apimodels.RuleResponse + require.NoError(t, json.Unmarshal(resp.Body(), &res)) + require.Equal(t, "success", res.Status) + + actualUIDs := []string{} + for _, group := range res.Data.RuleGroups { + for _, rule := range group.Rules { + actualUIDs = append(actualUIDs, rule.UID) + } + } + + require.ElementsMatch(t, tc.expectedUIDs, actualUIDs) + }) + } + }) + + t.Run("pagination with rule_matcher in-memory filtering", func(t *testing.T) { + fakeStore, fakeAIM, api := setupAPI(t) + + // Create 3 groups with 2 rules each: + // Group 1 & 2: team=backend (won't match filter) + // Group 3: team=frontend (will match filter) + // This tests that pagination continues fetching when early pages are filtered out + + group1Key := ngmodels.AlertRuleGroupKey{OrgID: orgID, NamespaceUID: "namespace1", RuleGroup: "group1"} + group2Key := ngmodels.AlertRuleGroupKey{OrgID: orgID, NamespaceUID: "namespace2", RuleGroup: "group2"} + group3Key := ngmodels.AlertRuleGroupKey{OrgID: orgID, NamespaceUID: "namespace3", RuleGroup: "group3"} + + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithUID("rule1"), gen.WithLabels(map[string]string{"team": "security"}), gen.WithGroupKey(group1Key), gen.WithNoNotificationSettings()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithUID("rule2"), gen.WithLabels(map[string]string{"team": "security"}), gen.WithGroupKey(group1Key), gen.WithNoNotificationSettings()) + + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithUID("rule3"), gen.WithLabels(map[string]string{"team": "security"}), gen.WithGroupKey(group2Key), gen.WithNoNotificationSettings()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithUID("rule4"), gen.WithLabels(map[string]string{"team": "security"}), gen.WithGroupKey(group2Key), gen.WithNoNotificationSettings()) + + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithUID("rule5"), gen.WithLabels(map[string]string{"team": "alerting"}), gen.WithGroupKey(group3Key), gen.WithNoNotificationSettings()) + generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery(), + gen.WithUID("rule6"), gen.WithLabels(map[string]string{"team": "alerting"}), gen.WithGroupKey(group3Key), gen.WithNoNotificationSettings()) + + // Request with regex rule_matcher filter for team=~"alerting" and group_limit=1 to force pagination + matcher := `{"name":"team","value":"alerting","isRegex":true,"isEqual":true}` + reqURL := "/api/v1/rules?rule_matcher=" + url.QueryEscape(matcher) + "&group_limit=1" + + req, err := http.NewRequest("GET", reqURL, nil) + require.NoError(t, err) + ctx := &contextmodel.ReqContext{ + Context: &web.Context{Req: req}, + SignedInUser: &user.SignedInUser{OrgID: orgID, Permissions: queryPermissions}, + } + + resp := api.RouteGetRuleStatuses(ctx) + require.Equal(t, http.StatusOK, resp.Status()) + + var res apimodels.RuleResponse + require.NoError(t, json.Unmarshal(resp.Body(), &res)) + require.Equal(t, "success", res.Status) + + actualUIDs := []string{} + for _, group := range res.Data.RuleGroups { + for _, rule := range group.Rules { + actualUIDs = append(actualUIDs, rule.UID) + } + } + + // Should return group3 rules (rule5, rule6), pagination should continue past filtered groups + require.ElementsMatch(t, []string{"rule5", "rule6"}, actualUIDs) + }) } func setupAPI(t *testing.T) (*fakes.RuleStore, *fakeAlertInstanceManager, PrometheusSrv) { diff --git a/pkg/services/ngalert/api/prometheus/api_prometheus.go b/pkg/services/ngalert/api/prometheus/api_prometheus.go index ddbd1d2af4c..934805d74f4 100644 --- a/pkg/services/ngalert/api/prometheus/api_prometheus.go +++ b/pkg/services/ngalert/api/prometheus/api_prometheus.go @@ -33,6 +33,12 @@ import ( "go.opentelemetry.io/otel/trace" ) +const ( + queryIncludeInternalLabels = "includeInternalLabels" + queryRuleMatcher = "rule_matcher" + queryInstanceMatcher = "matcher" +) + type RuleStoreReader interface { GetUserVisibleNamespaces(context.Context, int64, identity.Requester) (map[string]*folder.Folder, error) ListAlertRulesStoreV2 @@ -62,6 +68,20 @@ type PrometheusSrv struct { // Package-level OpenTelemetry tracer per Grafana instrumentation conventions. var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/ngalert/api/prometheus") +// badRequestError returns a Prometheus-compatible error response for bad request data. +func badRequestError(err error) apimodels.RuleResponse { + return apimodels.RuleResponse{ + DiscoveryBase: apimodels.DiscoveryBase{ + Status: "error", + Error: err.Error(), + ErrorType: apiv1.ErrBadData, + }, + Data: apimodels.RuleDiscovery{ + RuleGroups: []apimodels.RuleGroup{}, + }, + } +} + func NewPrometheusSrv(log log.Logger, manager state.AlertInstanceManager, status StatusReader, store RuleStoreReader, authz RuleGroupAccessControlService, provenanceStore ProvenanceStore) *PrometheusSrv { return &PrometheusSrv{ log, @@ -73,8 +93,6 @@ func NewPrometheusSrv(log log.Logger, manager state.AlertInstanceManager, status } } -const queryIncludeInternalLabels = "includeInternalLabels" - func getBoolWithDefault(vals url.Values, field string, d bool) bool { f := vals.Get(field) if f == "" { @@ -188,15 +206,15 @@ func getPanelIDFromQuery(v url.Values) (int64, error) { return 0, nil } -func getMatchersFromQuery(v url.Values) (labels.Matchers, error) { +func getMatchersFromQuery(v url.Values, paramName string) (labels.Matchers, error) { var matchers labels.Matchers - for _, s := range v["matcher"] { + for _, s := range v[paramName] { var m labels.Matcher if err := json.Unmarshal([]byte(s), &m); err != nil { return nil, err } if len(m.Name) == 0 { - return nil, errors.New("bad matcher: the name cannot be blank") + return nil, fmt.Errorf("bad %s: the name cannot be blank", paramName) } matchers = append(matchers, &m) } @@ -454,6 +472,7 @@ type paginationContext struct { stateFilterSet map[eval.State]struct{} healthFilterSet map[string]struct{} matchers labels.Matchers + ruleLabelMatchers labels.Matchers labelOptions []ngmodels.LabelOption limitAlertsPerRule int64 limitRulesPerGroup int64 @@ -476,6 +495,9 @@ func accumulateTotals(dest, source map[string]int64) { // fetchAndFilterPage fetches one page from the store and applies filters func (ctx *paginationContext) fetchAndFilterPage(log log.Logger, store ListAlertRulesStoreV2, span trace.Span, token string, remainingGroups, remainingRules int64) (pageResult, error) { + // Split matchers: only equality/inequality are supported by the store + storeMatchers := filterOutRegexMatchers(ctx.ruleLabelMatchers) + byGroupQuery := ngmodels.ListAlertRulesExtendedQuery{ ListAlertRulesQuery: ngmodels.ListAlertRulesQuery{ OrgID: ctx.opts.OrgID, @@ -488,6 +510,7 @@ func (ctx *paginationContext) fetchAndFilterPage(log log.Logger, store ListAlert DataSourceUIDs: ctx.dataSourceUIDs, SearchTitle: ctx.title, SearchRuleGroup: ctx.searchRuleGroup, + LabelMatchers: storeMatchers, }, RuleType: ctx.ruleType, Limit: remainingGroups, @@ -534,6 +557,8 @@ func (ctx *paginationContext) fetchAndFilterPage(log log.Logger, store ListAlert filterRulesByHealth(ruleGroup, ctx.healthFilterSet) } + filterRulesByLabelMatchers(ruleGroup, ctx.ruleLabelMatchers) + if ctx.limitRulesPerGroup > -1 && int64(len(ruleGroup.Rules)) > ctx.limitRulesPerGroup { ruleGroup.Rules = ruleGroup.Rules[0:ctx.limitRulesPerGroup] } @@ -546,6 +571,17 @@ func (ctx *paginationContext) fetchAndFilterPage(log log.Logger, store ListAlert return result, nil } +func filterOutRegexMatchers(matchers labels.Matchers) labels.Matchers { + var result labels.Matchers + for _, m := range matchers { + if m.Type == labels.MatchEqual || m.Type == labels.MatchNotEqual { + result = append(result, m) + } + } + + return result +} + // paginateRuleGroups fetches pages until limits are satisfied applying filters at each step func paginateRuleGroups(log log.Logger, store ListAlertRulesStoreV2, ctx *paginationContext, span trace.Span, maxGroups, maxRules int64, startToken string) ([]apimodels.RuleGroup, map[string]int64, string, error) { allGroups := []apimodels.RuleGroup{} @@ -644,21 +680,30 @@ func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opt attribute.Int64("limit_rules", limitRulesPerGroup), attribute.Int64("limit_alerts", limitAlertsPerRule), ) - matchers, err := getMatchersFromQuery(opts.Query) + matchers, err := getMatchersFromQuery(opts.Query, queryInstanceMatcher) if err != nil { - ruleResponse.Status = "error" - ruleResponse.Error = err.Error() - ruleResponse.ErrorType = apiv1.ErrBadData - return ruleResponse + return badRequestError(err) } span.SetAttributes(attribute.Int("matcher_count", len(matchers))) + ruleLabelMatchers, err := getMatchersFromQuery(opts.Query, queryRuleMatcher) + if err != nil { + return badRequestError(err) + } + regexCount := 0 + for _, m := range ruleLabelMatchers { + if m.Type == labels.MatchRegexp || m.Type == labels.MatchNotRegexp { + regexCount++ + } + } + span.SetAttributes( + attribute.Int("rule_matcher_count", len(ruleLabelMatchers)), + attribute.Int("rule_matcher_regex_count", regexCount), + ) + stateFilterSet, err := GetStatesFromQuery(opts.Query) if err != nil { - ruleResponse.Status = "error" - ruleResponse.Error = err.Error() - ruleResponse.ErrorType = apiv1.ErrBadData - return ruleResponse + return badRequestError(err) } span.SetAttributes( attribute.Int("state_filter_count", len(stateFilterSet)), @@ -667,10 +712,7 @@ func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opt healthFilterSet, err := GetHealthFromQuery(opts.Query) if err != nil { - ruleResponse.Status = "error" - ruleResponse.Error = err.Error() - ruleResponse.ErrorType = apiv1.ErrBadData - return ruleResponse + return badRequestError(err) } span.SetAttributes( attribute.Int("health_filter_count", len(healthFilterSet)), @@ -808,6 +850,7 @@ func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opt stateFilterSet: stateFilterSet, healthFilterSet: healthFilterSet, matchers: matchers, + ruleLabelMatchers: ruleLabelMatchers, labelOptions: labelOptions, limitAlertsPerRule: limitAlertsPerRule, limitRulesPerGroup: limitRulesPerGroup, @@ -833,6 +876,7 @@ func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opt return ruleResponse } +// nolint:gocyclo func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts RuleGroupStatusesOptions, ruleStatusMutator RuleStatusMutator, alertStateMutator RuleAlertStateMutator, provenanceRecords map[string]ngmodels.Provenance) apimodels.RuleResponse { ruleResponse := apimodels.RuleResponse{ DiscoveryBase: apimodels.DiscoveryBase{ @@ -846,41 +890,30 @@ func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts Ru dashboardUID := opts.Query.Get("dashboard_uid") panelID, err := getPanelIDFromQuery(opts.Query) if err != nil { - ruleResponse.Status = "error" - ruleResponse.Error = fmt.Sprintf("invalid panel_id: %s", err.Error()) - ruleResponse.ErrorType = apiv1.ErrBadData - return ruleResponse + return badRequestError(fmt.Errorf("invalid panel_id: %w", err)) } if dashboardUID == "" && panelID != 0 { - ruleResponse.Status = "error" - ruleResponse.Error = "panel_id must be set with dashboard_uid" - ruleResponse.ErrorType = apiv1.ErrBadData - return ruleResponse + return badRequestError(errors.New("panel_id must be set with dashboard_uid")) } limitRulesPerGroup := getInt64WithDefault(opts.Query, "limit_rules", -1) limitAlertsPerRule := getInt64WithDefault(opts.Query, "limit_alerts", -1) - matchers, err := getMatchersFromQuery(opts.Query) + matchers, err := getMatchersFromQuery(opts.Query, queryInstanceMatcher) if err != nil { - ruleResponse.Status = "error" - ruleResponse.Error = err.Error() - ruleResponse.ErrorType = apiv1.ErrBadData - return ruleResponse + return badRequestError(err) + } + ruleLabelMatchers, err := getMatchersFromQuery(opts.Query, queryRuleMatcher) + if err != nil { + return badRequestError(err) } stateFilterSet, err := GetStatesFromQuery(opts.Query) if err != nil { - ruleResponse.Status = "error" - ruleResponse.Error = err.Error() - ruleResponse.ErrorType = apiv1.ErrBadData - return ruleResponse + return badRequestError(err) } healthFilterSet, err := GetHealthFromQuery(opts.Query) if err != nil { - ruleResponse.Status = "error" - ruleResponse.Error = err.Error() - ruleResponse.ErrorType = apiv1.ErrBadData - return ruleResponse + return badRequestError(err) } var labelOptions []ngmodels.LabelOption @@ -913,6 +946,9 @@ func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts Ru dataSourceUIDs := opts.Query["datasource_uid"] searchRuleGroup := opts.Query.Get("search.rule_group") + // Split matchers: only equality/inequality are supported by the store + storeMatchers := filterOutRegexMatchers(ruleLabelMatchers) + alertRuleQuery := ngmodels.ListAlertRulesQuery{ OrgID: opts.OrgID, NamespaceUIDs: namespaceUIDs, @@ -924,6 +960,7 @@ func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts Ru SearchTitle: title, SearchRuleGroup: searchRuleGroup, DataSourceUIDs: dataSourceUIDs, + LabelMatchers: storeMatchers, } ruleList, err := store.ListAlertRules(opts.Ctx, &alertRuleQuery) if err != nil { @@ -978,6 +1015,10 @@ func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts Ru filterRulesByHealth(ruleGroup, healthFilterSet) } + if len(ruleLabelMatchers) > 0 { + filterRulesByLabelMatchers(ruleGroup, ruleLabelMatchers) + } + if limitRulesPerGroup > -1 && int64(len(ruleGroup.Rules)) > limitRulesPerGroup { ruleGroup.Rules = ruleGroup.Rules[0:limitRulesPerGroup] } @@ -1105,6 +1146,30 @@ func filterRulesByHealth(ruleGroup *apimodels.RuleGroup, withHealthFast map[stri ruleGroup.Rules = filteredRules } +func filterRulesByLabelMatchers(ruleGroup *apimodels.RuleGroup, matchers labels.Matchers) { + if len(matchers) == 0 { + return + } + + filteredRules := make([]apimodels.AlertingRule, 0, len(ruleGroup.Rules)) + + for _, rule := range ruleGroup.Rules { + ruleLabels := rule.Labels.Map() + matches := true + for _, m := range matchers { + if !m.Matches(ruleLabels[m.Name]) { + matches = false + break + } + } + if matches { + filteredRules = append(filteredRules, rule) + } + } + + ruleGroup.Rules = filteredRules +} + // This is the same as matchers.Matches but avoids the need to create a LabelSet func matchersMatch(matchers []*labels.Matcher, labels map[string]string) bool { for _, m := range matchers { diff --git a/pkg/services/ngalert/api/tooling/definitions/prom.go b/pkg/services/ngalert/api/tooling/definitions/prom.go index 128c69787c0..05fb62dc283 100644 --- a/pkg/services/ngalert/api/tooling/definitions/prom.go +++ b/pkg/services/ngalert/api/tooling/definitions/prom.go @@ -462,4 +462,10 @@ type GetGrafanaRuleStatusesParams struct { // in: query // required: false Matchers []string `json:"matcher"` + + // Filter rules by their static labels (not alert instance labels). Each value is a JSON-encoded Prometheus-like matcher (for example, {"type":0,"name":"severity","value":"critical"}). + // For equality matchers with empty string values (e.g., name=""), rules that have the label with an empty value OR rules without the label will match (standard Prometheus behavior). + // in: query + // required: false + RuleLabelMatchers []string `json:"rule_matcher"` } diff --git a/pkg/services/ngalert/api/tooling/post.json b/pkg/services/ngalert/api/tooling/post.json index d9d38ce03b0..1243007dfcf 100644 --- a/pkg/services/ngalert/api/tooling/post.json +++ b/pkg/services/ngalert/api/tooling/post.json @@ -7561,6 +7561,15 @@ }, "name": "matcher", "type": "array" + }, + { + "description": "Filter rules by their static labels (not alert instance labels). Each value is a JSON-encoded Prometheus-like matcher (for example, {\"type\":0,\"name\":\"severity\",\"value\":\"critical\"}).\nFor equality matchers with empty string values (e.g., name=\"\"), rules that have the label with an empty value OR rules without the label will match (standard Prometheus behavior).", + "in": "query", + "items": { + "type": "string" + }, + "name": "rule_matcher", + "type": "array" } ], "responses": { diff --git a/pkg/services/ngalert/api/tooling/spec.json b/pkg/services/ngalert/api/tooling/spec.json index adb65e92ad9..81e0aa894c9 100644 --- a/pkg/services/ngalert/api/tooling/spec.json +++ b/pkg/services/ngalert/api/tooling/spec.json @@ -1947,6 +1947,15 @@ "description": "Filter by label matchers encoded as JSON representations of Prometheus matchers (for example, {\"type\":0,\"name\":\"severity\",\"value\":\"critical\"}). Provide one matcher per query string value.", "name": "matcher", "in": "query" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "description": "Filter rules by their static labels (not alert instance labels). Each value is a JSON-encoded Prometheus-like matcher (for example, {\"type\":0,\"name\":\"severity\",\"value\":\"critical\"}).\nFor equality matchers with empty string values (e.g., name=\"\"), rules that have the label with an empty value OR rules without the label will match (standard Prometheus behavior).", + "name": "rule_matcher", + "in": "query" } ], "responses": { diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index 14da686b32f..f7bb3d9fcfd 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -18,6 +18,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/prometheus/alertmanager/pkg/labels" prommodels "github.com/prometheus/common/model" "github.com/grafana/grafana-plugin-sdk-go/data" @@ -1012,6 +1013,10 @@ type ListAlertRulesQuery struct { SearchRuleGroup string HasPrometheusRuleDefinition *bool + + // LabelMatchers filters rules by their labels. + // Only equality and inequality matchers are supported, no regex operators. + LabelMatchers labels.Matchers } type ListAlertRulesExtendedQuery struct { diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index 649af476741..4307778f9c6 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/google/uuid" + "github.com/prometheus/alertmanager/pkg/labels" "golang.org/x/exp/maps" "github.com/grafana/grafana/pkg/util/xorm" @@ -802,6 +803,15 @@ func (st DBstore) ListAlertRulesPaginated(ctx context.Context, query *ngmodels.L return result, nextToken, err } +func matchersMatchLabels(matchers labels.Matchers, lbls map[string]string) bool { + for _, m := range matchers { + if !m.Matches(lbls[m.Name]) { + return false + } + } + return true +} + // nolint:gocyclo func (st DBstore) buildListAlertRulesQuery(sess *db.Session, query *ngmodels.ListAlertRulesExtendedQuery) (q *xorm.Session, groupsSet map[string]struct{}, err error) { q = sess.Table("alert_rule") @@ -920,6 +930,13 @@ func (st DBstore) buildListAlertRulesQuery(sess *db.Session, query *ngmodels.Lis } } + if len(query.LabelMatchers) > 0 { + q, err = st.filterByLabelMatchers(query.LabelMatchers, q) + if err != nil { + return nil, groupsSet, err + } + } + // FIXME: record is nullable but we don't save it as null when it's nil switch query.RuleType { case ngmodels.RuleTypeFilterAlerting: @@ -967,6 +984,11 @@ func (st DBstore) handleRuleRow(rows *xorm.Rows, query *ngmodels.ListAlertRulesE return nil, false } } + if len(query.LabelMatchers) > 0 { // remove false-positive hits from the result + if !matchersMatchLabels(query.LabelMatchers, converted.Labels) { + return nil, false + } + } // MySQL (and potentially other databases) can use case-insensitive comparison. // This code makes sure we return groups that only exactly match the filter. if groupsSet != nil { @@ -1355,6 +1377,23 @@ func (st DBstore) filterWithPrometheusRuleDefinition(value bool, sess *xorm.Sess ), nil } +// filterByLabelMatchers adds filtering for equality and inequality label matchers. +// Returns error if regex matchers are passed. +func (st DBstore) filterByLabelMatchers(matchers labels.Matchers, sess *xorm.Session) (*xorm.Session, error) { + for _, m := range matchers { + if m.Type != labels.MatchEqual && m.Type != labels.MatchNotEqual { + return nil, fmt.Errorf("matcher %q %s %q is not supported", m.Name, m.Type, m.Value) + } + + sql, args, err := buildLabelMatcherCondition(st.SQLStore.GetDialect(), "labels", m) + if err != nil { + return nil, err + } + sess = sess.And(sql, args...) + } + return sess, nil +} + func (st DBstore) RenameReceiverInNotificationSettings(ctx context.Context, orgID int64, oldReceiver, newReceiver string, validateProvenance func(ngmodels.Provenance) bool, dryRun bool) ([]ngmodels.AlertRuleKey, []ngmodels.AlertRuleKey, error) { // fetch entire rules because Update method requires it because it copies rules to version table rules, err := st.ListAlertRules(ctx, &ngmodels.ListAlertRulesQuery{ diff --git a/pkg/services/ngalert/store/alert_rule_labels.go b/pkg/services/ngalert/store/alert_rule_labels.go new file mode 100644 index 00000000000..721071d5219 --- /dev/null +++ b/pkg/services/ngalert/store/alert_rule_labels.go @@ -0,0 +1,51 @@ +package store + +import ( + "fmt" + + "github.com/prometheus/alertmanager/pkg/labels" + + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" +) + +// buildLabelMatcherCondition builds SQL for a label matcher with Prometheus semantics. +// For MySQL/PostgreSQL, it uses JSON functions, and +// for SQLite, it uses GLOB patterns to find matching labels. +func buildLabelMatcherCondition(dialect migrator.Dialect, column string, m *labels.Matcher) (string, []any, error) { + if dialect.DriverName() == migrator.SQLite { + return buildLabelMatcherGlob(column, m) + } + return buildLabelMatcherJSON(dialect, column, m) +} + +func buildLabelMatcherGlob(column string, m *labels.Matcher) (string, []any, error) { + switch { + case m.Type == labels.MatchEqual && m.Value == "": + eqSQL, eqArgs, _ := globEquals(column, m.Name, "") + missingSQL, missingArgs, _ := globKeyMissing(column, m.Name) + return "(" + eqSQL + " OR " + missingSQL + ")", append(eqArgs, missingArgs...), nil + case m.Type == labels.MatchEqual: + return globEquals(column, m.Name, m.Value) + case m.Type == labels.MatchNotEqual: + return globNotEquals(column, m.Name, m.Value) + default: + return "", nil, fmt.Errorf("unsupported matcher type: %v", m.Type) + } +} + +func buildLabelMatcherJSON(dialect migrator.Dialect, column string, m *labels.Matcher) (string, []any, error) { + switch { + case m.Type == labels.MatchEqual && m.Value == "": + eqSQL, eqArgs := jsonEquals(dialect, column, m.Name, "") + missingSQL, missingArgs := jsonKeyMissing(dialect, column, m.Name) + return "(" + eqSQL + " OR " + missingSQL + ")", append(eqArgs, missingArgs...), nil + case m.Type == labels.MatchEqual: + sql, args := jsonEquals(dialect, column, m.Name, m.Value) + return sql, args, nil + case m.Type == labels.MatchNotEqual: + sql, args := jsonNotEquals(dialect, column, m.Name, m.Value) + return sql, args, nil + default: + return "", nil, fmt.Errorf("unsupported matcher type: %v", m.Type) + } +} diff --git a/pkg/services/ngalert/store/alert_rule_labels_test.go b/pkg/services/ngalert/store/alert_rule_labels_test.go new file mode 100644 index 00000000000..9b72d8f00f9 --- /dev/null +++ b/pkg/services/ngalert/store/alert_rule_labels_test.go @@ -0,0 +1,136 @@ +package store + +import ( + "testing" + + "github.com/prometheus/alertmanager/pkg/labels" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" +) + +func TestBuildLabelMatcherGlob(t *testing.T) { + tests := []struct { + name string + matcher *labels.Matcher + wantSQL string + wantArgs []any + wantErr bool + errContains string + }{ + { + name: "MatchEqual with non-empty value", + matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: "alerting"}, + wantSQL: "labels GLOB ?", + wantArgs: []any{`*"team":"alerting"*`}, + }, + { + name: "MatchEqual with empty value (Prometheus semantics)", + matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: ""}, + wantSQL: `(labels GLOB ? OR labels NOT GLOB ?)`, + wantArgs: []any{`*"team":""*`, `*"team":*`}, + }, + { + name: "MatchNotEqual", + matcher: &labels.Matcher{Type: labels.MatchNotEqual, Name: "team", Value: "alerting"}, + wantSQL: "labels NOT GLOB ?", + wantArgs: []any{`*"team":"alerting"*`}, + }, + { + name: "unsupported matcher type", + matcher: &labels.Matcher{Type: labels.MatchRegexp, Name: "team", Value: "alert.*"}, + wantErr: true, + errContains: "unsupported matcher type", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sql, args, err := buildLabelMatcherGlob("labels", tt.matcher) + if tt.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tt.errContains) + return + } + require.NoError(t, err) + require.Equal(t, tt.wantSQL, sql) + require.Equal(t, tt.wantArgs, args) + }) + } +} + +func TestBuildLabelMatcherJSON(t *testing.T) { + tests := []struct { + name string + dialect migrator.Dialect + matcher *labels.Matcher + wantSQL string + wantArgs []any + wantErr bool + errContains string + }{ + { + name: "MySQL MatchEqual with non-empty value", + dialect: migrator.NewMysqlDialect(), + matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: "alerting"}, + wantSQL: "JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) = ?", + wantArgs: []any{"team", "alerting"}, + }, + { + name: "MySQL MatchEqual with empty value", + dialect: migrator.NewMysqlDialect(), + matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: ""}, + wantSQL: "(JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) = ? OR JSON_EXTRACT(labels, CONCAT('$.', ?)) IS NULL)", + wantArgs: []any{"team", "", "team"}, + }, + { + name: "MySQL MatchNotEqual", + dialect: migrator.NewMysqlDialect(), + matcher: &labels.Matcher{Type: labels.MatchNotEqual, Name: "team", Value: "alerting"}, + wantSQL: "(JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) IS NULL OR JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) != ?)", + wantArgs: []any{"team", "team", "alerting"}, + }, + { + name: "PostgreSQL MatchEqual with non-empty value", + dialect: migrator.NewPostgresDialect(), + matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: "alerting"}, + wantSQL: "jsonb_extract_path_text(labels::jsonb, ?) = ?", + wantArgs: []any{"team", "alerting"}, + }, + { + name: "PostgreSQL MatchEqual with empty value", + dialect: migrator.NewPostgresDialect(), + matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: ""}, + wantSQL: "(jsonb_extract_path_text(labels::jsonb, ?) = ? OR jsonb_extract_path_text(labels::jsonb, ?) IS NULL)", + wantArgs: []any{"team", "", "team"}, + }, + { + name: "PostgreSQL MatchNotEqual", + dialect: migrator.NewPostgresDialect(), + matcher: &labels.Matcher{Type: labels.MatchNotEqual, Name: "team", Value: "alerting"}, + wantSQL: "(jsonb_extract_path_text(labels::jsonb, ?) IS NULL OR jsonb_extract_path_text(labels::jsonb, ?) != ?)", + wantArgs: []any{"team", "team", "alerting"}, + }, + { + name: "unsupported matcher type", + dialect: migrator.NewMysqlDialect(), + matcher: &labels.Matcher{Type: labels.MatchRegexp, Name: "team", Value: "alert.*"}, + wantErr: true, + errContains: "unsupported matcher type", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sql, args, err := buildLabelMatcherJSON(tt.dialect, "labels", tt.matcher) + if tt.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tt.errContains) + return + } + require.NoError(t, err) + require.Equal(t, tt.wantSQL, sql) + require.Equal(t, tt.wantArgs, args) + }) + } +} diff --git a/pkg/services/ngalert/store/alert_rule_test.go b/pkg/services/ngalert/store/alert_rule_test.go index 93f6f9e57a4..91db9edc32a 100644 --- a/pkg/services/ngalert/store/alert_rule_test.go +++ b/pkg/services/ngalert/store/alert_rule_test.go @@ -13,6 +13,7 @@ import ( "github.com/benbjohnson/clock" "github.com/google/uuid" + "github.com/prometheus/alertmanager/pkg/labels" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -2385,6 +2386,157 @@ func TestIntegration_ListAlertRules(t *testing.T) { }) } }) + + t.Run("filter by LabelMatchers", func(t *testing.T) { + sqlStore := db.InitTestDB(t) + folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()) + store := createTestStore(sqlStore, folderService, &logtest.Fake{}, cfg.UnifiedAlerting, b) + + ruleLower := createRule(t, store, ruleGen.With( + ruleGen.WithLabels(map[string]string{"team": "alerting", "severity": "warning"}), + ruleGen.WithTitle("rule_lowercase"))) + ruleUpper := createRule(t, store, ruleGen.With( + ruleGen.WithLabels(map[string]string{"team": "Alerting", "severity": "critical"}), + ruleGen.WithTitle("rule_uppercase"))) + ruleSpecial := createRule(t, store, ruleGen.With( + ruleGen.WithLabels(map[string]string{"key": `value"with"quotes`}), + ruleGen.WithTitle("rule_special"))) + ruleGlob := createRule(t, store, ruleGen.With( + ruleGen.WithLabels(map[string]string{"glob": "*[?]"}), + ruleGen.WithTitle("rule_glob"))) + ruleSpecialChars := createRule(t, store, ruleGen.With( + ruleGen.WithLabels(map[string]string{"json": "line1\nline2\\end\"quote"}), + ruleGen.WithTitle("rule_special_chars"))) + ruleEmpty := createRule(t, store, ruleGen.With( + ruleGen.WithLabels(map[string]string{"empty": ""}), + ruleGen.WithTitle("rule_empty"))) + ruleNonempty := createRule(t, store, ruleGen.With( + ruleGen.WithLabels(map[string]string{"empty": "nonempty"}), + ruleGen.WithTitle("rule_nonempty"))) + + tc := []struct { + name string + labelMatchers labels.Matchers + expectedRules []*models.AlertRule + }{ + { + name: "equality matcher is case-sensitive", + labelMatchers: labels.Matchers{ + func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchEqual, "team", "alerting"); return m }(), + }, + expectedRules: []*models.AlertRule{ruleLower}, + }, + { + name: "equality matcher matches uppercase when specified", + labelMatchers: labels.Matchers{ + func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchEqual, "team", "Alerting"); return m }(), + }, + expectedRules: []*models.AlertRule{ruleUpper}, + }, + { + name: "inequality matcher is case-sensitive", + labelMatchers: labels.Matchers{ + func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchNotEqual, "team", "alerting"); return m }(), + }, + expectedRules: []*models.AlertRule{ruleUpper, ruleSpecial, ruleGlob, ruleSpecialChars, ruleEmpty, ruleNonempty}, + }, + { + name: "special characters in labels are handled correctly", + labelMatchers: labels.Matchers{ + func() *labels.Matcher { + m, _ := labels.NewMatcher(labels.MatchEqual, "key", `value"with"quotes`) + return m + }(), + }, + expectedRules: []*models.AlertRule{ruleSpecial}, + }, + { + name: "matcher with non-existent label returns no rules", + labelMatchers: labels.Matchers{ + func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchEqual, "nonexistent", "value"); return m }(), + }, + expectedRules: []*models.AlertRule{}, + }, + { + name: "multiple matchers are ANDed", + labelMatchers: labels.Matchers{ + func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchEqual, "team", "Alerting"); return m }(), + func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchEqual, "severity", "critical"); return m }(), + }, + expectedRules: []*models.AlertRule{ruleUpper}, + }, + { + name: "GLOB special characters are escaped correctly", + labelMatchers: labels.Matchers{ + func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchEqual, "glob", "*[?]"); return m }(), + }, + expectedRules: []*models.AlertRule{ruleGlob}, + }, + { + name: "JSON escape characters are handled correctly", + labelMatchers: labels.Matchers{ + func() *labels.Matcher { + m, _ := labels.NewMatcher(labels.MatchEqual, "json", "line1\nline2\\end\"quote") + return m + }(), + }, + expectedRules: []*models.AlertRule{ruleSpecialChars}, + }, + { + name: "empty string value matches correctly", + labelMatchers: labels.Matchers{ + func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchEqual, "empty", ""); return m }(), + }, + expectedRules: []*models.AlertRule{ruleLower, ruleUpper, ruleSpecial, ruleGlob, ruleSpecialChars, ruleEmpty}, + }, + { + name: "inequality matcher on non-existent label matches all rules", + labelMatchers: labels.Matchers{ + func() *labels.Matcher { + m, _ := labels.NewMatcher(labels.MatchNotEqual, "nonexistent", "value") + return m + }(), + }, + expectedRules: []*models.AlertRule{ruleLower, ruleUpper, ruleSpecial, ruleGlob, ruleSpecialChars, ruleEmpty, ruleNonempty}, + }, + } + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + query := &models.ListAlertRulesQuery{ + OrgID: orgID, + LabelMatchers: tt.labelMatchers, + } + result, err := store.ListAlertRules(context.Background(), query) + require.NoError(t, err) + require.ElementsMatch(t, tt.expectedRules, result) + }) + } + + t.Run("regex matcher returns error from store", func(t *testing.T) { + query := &models.ListAlertRulesQuery{ + OrgID: orgID, + LabelMatchers: labels.Matchers{ + func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchRegexp, "team", "alert.*"); return m }(), + }, + } + _, err := store.ListAlertRules(context.Background(), query) + require.Error(t, err) + require.ErrorContains(t, err, "is not supported") + }) + + t.Run("not-regex matcher returns error from store", func(t *testing.T) { + query := &models.ListAlertRulesQuery{ + OrgID: orgID, + LabelMatchers: labels.Matchers{ + func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchNotRegexp, "team", "alert.*"); return m }(), + }, + } + _, err := store.ListAlertRules(context.Background(), query) + require.Error(t, err) + require.ErrorContains(t, err, "is not supported") + }) + }) } func TestIntegration_ListAlertRulesPaginated(t *testing.T) { diff --git a/pkg/services/ngalert/store/json.go b/pkg/services/ngalert/store/json.go new file mode 100644 index 00000000000..7b634246951 --- /dev/null +++ b/pkg/services/ngalert/store/json.go @@ -0,0 +1,101 @@ +package store + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" +) + +// JSON functions for MySQL/PostgreSQL + +func jsonEquals(dialect migrator.Dialect, column, key, value string) (string, []any) { + switch dialect.DriverName() { + case migrator.MySQL: + return fmt.Sprintf("JSON_UNQUOTE(JSON_EXTRACT(%s, CONCAT('$.', ?))) = ?", column), []any{key, value} + case migrator.Postgres: + return fmt.Sprintf("jsonb_extract_path_text(%s::jsonb, ?) = ?", column), []any{key, value} + default: + return "", nil + } +} + +func jsonNotEquals(dialect migrator.Dialect, column, key, value string) (string, []any) { + var jx string + switch dialect.DriverName() { + case migrator.MySQL: + jx = fmt.Sprintf("JSON_UNQUOTE(JSON_EXTRACT(%s, CONCAT('$.', ?)))", column) + case migrator.Postgres: + jx = fmt.Sprintf("jsonb_extract_path_text(%s::jsonb, ?)", column) + default: + return "", nil + } + return fmt.Sprintf("(%s IS NULL OR %s != ?)", jx, jx), []any{key, key, value} +} + +func jsonKeyMissing(dialect migrator.Dialect, column, key string) (string, []any) { + switch dialect.DriverName() { + case migrator.MySQL: + return fmt.Sprintf("JSON_EXTRACT(%s, CONCAT('$.', ?)) IS NULL", column), []any{key} + case migrator.Postgres: + return fmt.Sprintf("jsonb_extract_path_text(%s::jsonb, ?) IS NULL", column), []any{key} + default: + return "", nil + } +} + +// GLOB functions for SQLite + +func globEquals(column, key, value string) (string, []any, error) { + pattern, err := buildGlobPattern(key, value) + if err != nil { + return "", nil, err + } + return column + " GLOB ?", []any{"*" + pattern + "*"}, nil +} + +func globNotEquals(column, key, value string) (string, []any, error) { + pattern, err := buildGlobPattern(key, value) + if err != nil { + return "", nil, err + } + return column + " NOT GLOB ?", []any{"*" + pattern + "*"}, nil +} + +func globKeyMissing(column, key string) (string, []any, error) { + pattern, err := buildGlobKeyPattern(key) + if err != nil { + return "", nil, err + } + return column + " NOT GLOB ?", []any{"*" + pattern + "*"}, nil +} + +// Search for `"key":"value"` +func buildGlobPattern(key, value string) (string, error) { + keyJSON, err := json.Marshal(key) + if err != nil { + return "", fmt.Errorf("failed to marshal key: %w", err) + } + valueJSON, err := json.Marshal(value) + if err != nil { + return "", fmt.Errorf("failed to marshal value: %w", err) + } + return escapeGlobPattern(fmt.Sprintf(`%s:%s`, string(keyJSON), string(valueJSON))), nil +} + +// Search for `"key":` +func buildGlobKeyPattern(key string) (string, error) { + keyJSON, err := json.Marshal(key) + if err != nil { + return "", fmt.Errorf("failed to marshal key: %w", err) + } + return escapeGlobPattern(string(keyJSON) + ":"), nil +} + +func escapeGlobPattern(pattern string) string { + pattern = strings.ReplaceAll(pattern, "[", "[[]") + pattern = strings.ReplaceAll(pattern, "*", "[*]") + pattern = strings.ReplaceAll(pattern, "?", "[?]") + return pattern +} diff --git a/pkg/services/ngalert/store/json_test.go b/pkg/services/ngalert/store/json_test.go new file mode 100644 index 00000000000..89f85a027a6 --- /dev/null +++ b/pkg/services/ngalert/store/json_test.go @@ -0,0 +1,185 @@ +package store + +import ( + "testing" + + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/stretchr/testify/require" +) + +func TestJsonEquals(t *testing.T) { + tests := []struct { + name string + dialect migrator.Dialect + column string + key string + value string + wantSQL string + wantArgs []any + }{ + { + name: "MySQL", + dialect: migrator.NewMysqlDialect(), + column: "labels", + key: "team", + value: "alerting", + wantSQL: "JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) = ?", + wantArgs: []any{"team", "alerting"}, + }, + { + name: "PostgreSQL", + dialect: migrator.NewPostgresDialect(), + column: "labels", + key: "team", + value: "alerting", + wantSQL: "jsonb_extract_path_text(labels::jsonb, ?) = ?", + wantArgs: []any{"team", "alerting"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sql, args := jsonEquals(tt.dialect, tt.column, tt.key, tt.value) + require.Equal(t, tt.wantSQL, sql) + require.Equal(t, tt.wantArgs, args) + }) + } +} + +func TestJsonNotEquals(t *testing.T) { + tests := []struct { + name string + dialect migrator.Dialect + column string + key string + value string + wantSQL string + wantArgs []any + }{ + { + name: "MySQL", + dialect: migrator.NewMysqlDialect(), + column: "labels", + key: "team", + value: "alerting", + wantSQL: "(JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) IS NULL OR JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) != ?)", + wantArgs: []any{"team", "team", "alerting"}, + }, + { + name: "PostgreSQL", + dialect: migrator.NewPostgresDialect(), + column: "labels", + key: "team", + value: "alerting", + wantSQL: "(jsonb_extract_path_text(labels::jsonb, ?) IS NULL OR jsonb_extract_path_text(labels::jsonb, ?) != ?)", + wantArgs: []any{"team", "team", "alerting"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sql, args := jsonNotEquals(tt.dialect, tt.column, tt.key, tt.value) + require.Equal(t, tt.wantSQL, sql) + require.Equal(t, tt.wantArgs, args) + }) + } +} + +func TestJsonKeyMissing(t *testing.T) { + tests := []struct { + name string + dialect migrator.Dialect + column string + key string + wantSQL string + wantArgs []any + }{ + { + name: "MySQL", + dialect: migrator.NewMysqlDialect(), + column: "labels", + key: "team", + wantSQL: "JSON_EXTRACT(labels, CONCAT('$.', ?)) IS NULL", + wantArgs: []any{"team"}, + }, + { + name: "PostgreSQL", + dialect: migrator.NewPostgresDialect(), + column: "labels", + key: "team", + wantSQL: "jsonb_extract_path_text(labels::jsonb, ?) IS NULL", + wantArgs: []any{"team"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sql, args := jsonKeyMissing(tt.dialect, tt.column, tt.key) + require.Equal(t, tt.wantSQL, sql) + require.Equal(t, tt.wantArgs, args) + }) + } +} + +func TestGlobEquals(t *testing.T) { + sql, args, err := globEquals("labels", "team", "alerting") + require.NoError(t, err) + require.Equal(t, "labels GLOB ?", sql) + require.Equal(t, []any{`*"team":"alerting"*`}, args) +} + +func TestGlobNotEquals(t *testing.T) { + sql, args, err := globNotEquals("labels", "team", "alerting") + require.NoError(t, err) + require.Equal(t, "labels NOT GLOB ?", sql) + require.Equal(t, []any{`*"team":"alerting"*`}, args) +} + +func TestGlobKeyMissing(t *testing.T) { + sql, args, err := globKeyMissing("labels", "team") + require.NoError(t, err) + require.Equal(t, "labels NOT GLOB ?", sql) + require.Equal(t, []any{`*"team":*`}, args) +} + +func TestBuildGlobPattern(t *testing.T) { + tests := []struct { + name string + key string + value string + expected string + }{ + { + name: "simple key-value", + key: "team", + value: "alerting", + expected: `"team":"alerting"`, + }, + { + name: "empty value", + key: "empty", + value: "", + expected: `"empty":""`, + }, + { + name: "special GLOB chars are escaped", + key: "key", + value: "*[?]", + expected: `"key":"[*][[][?]]"`, + }, + { + name: "special chars are escaped", + key: "key", + value: "line1\nline2\\end\"quote", + expected: `"key":"line1\nline2\\end\"quote"`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pattern, err := buildGlobPattern(tt.key, tt.value) + require.NoError(t, err) + require.Equal(t, tt.expected, pattern) + }) + } +} diff --git a/pkg/services/ngalert/tests/fakes/rules.go b/pkg/services/ngalert/tests/fakes/rules.go index cfe790b9853..42e30ddb052 100644 --- a/pkg/services/ngalert/tests/fakes/rules.go +++ b/pkg/services/ngalert/tests/fakes/rules.go @@ -219,6 +219,7 @@ func (f *RuleStore) ListAlertRulesByGroup(_ context.Context, q *models.ListAlert RuleUIDs: q.RuleUIDs, ReceiverName: q.ReceiverName, HasPrometheusRuleDefinition: q.HasPrometheusRuleDefinition, + LabelMatchers: q.LabelMatchers, } ruleList, err := f.listAlertRules(query) @@ -355,6 +356,20 @@ func (f *RuleStore) listAlertRules(q *models.ListAlertRulesQuery) (models.RulesG if q.ReceiverName != "" && (len(r.NotificationSettings) < 1 || r.NotificationSettings[0].Receiver != q.ReceiverName) { continue } + + if len(q.LabelMatchers) > 0 { + matches := true + for _, m := range q.LabelMatchers { + if !m.Matches(r.Labels[m.Name]) { + matches = false + break + } + } + if !matches { + continue + } + } + copyR := models.CopyRule(r) ruleList = append(ruleList, copyR) } diff --git a/pkg/tests/api/alerting/api_prometheus_test.go b/pkg/tests/api/alerting/api_prometheus_test.go index 61bc3195857..067372a3470 100644 --- a/pkg/tests/api/alerting/api_prometheus_test.go +++ b/pkg/tests/api/alerting/api_prometheus_test.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "net/http" + "net/url" "sort" "testing" "time" @@ -364,8 +365,6 @@ func TestIntegrationPrometheusRules(t *testing.T) { func TestIntegrationPrometheusRulesPagination(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) - testinfra.SQLiteIntegrationTest(t) - dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ DisableLegacyAlerting: true, EnableUnifiedAlerting: true, @@ -388,23 +387,30 @@ func TestIntegrationPrometheusRulesPagination(t *testing.T) { require.NoError(t, err) // Create 3 rule groups with different numbers of rules - // Group 1: 5 rules, Group 2: 3 rules, Group 3: 2 rules (total: 10 rules) + // Group 1: 5 rules with team=backend + // Group 2: 3 rules with team=frontend + // Group 3: 2 rules with team=platform for groupIdx := 1; groupIdx <= 3; groupIdx++ { var rulesCount int + var team string switch groupIdx { case 1: rulesCount = 5 + team = "backend" case 2: rulesCount = 3 + team = "frontend" case 3: rulesCount = 2 + team = "platform" } rules := make([]apimodels.PostableExtendedRuleNode, rulesCount) for i := 0; i < rulesCount; i++ { rules[i] = apimodels.PostableExtendedRuleNode{ ApiRuleNode: &apimodels.ApiRuleNode{ - For: &interval, + For: &interval, + Labels: map[string]string{"team": team}, }, GrafanaManagedAlert: &apimodels.PostableGrafanaRule{ Title: fmt.Sprintf("rule-%d-%d", groupIdx, i+1), @@ -514,6 +520,61 @@ func TestIntegrationPrometheusRulesPagination(t *testing.T) { require.Equal(t, http.StatusOK, resp.StatusCode) require.Len(t, result.Data.RuleGroups, 0, "should return no groups") }) + + t.Run("with rule_matcher filter returns only matching rules", func(t *testing.T) { + matcher := url.QueryEscape(`{"name":"team","value":"frontend","isRegex":false,"isEqual":true}`) + promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/prometheus/grafana/api/v1/rules?rule_matcher=%s", grafanaListedAddr, matcher) + // nolint:gosec + resp, err := http.Get(promRulesURL) + require.NoError(t, err) + t.Cleanup(func() { + err := resp.Body.Close() + require.NoError(t, err) + }) + + var result apimodels.RuleResponse + err = json.NewDecoder(resp.Body).Decode(&result) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) + + // Should only return group-2 (team=frontend, 3 rules) + foundGroups := []string{} + total := 0 + for _, group := range result.Data.RuleGroups { + foundGroups = append(foundGroups, group.Name) + total += len(group.Rules) + } + require.Equal(t, []string{"group-2"}, foundGroups) + require.Equal(t, 3, total) + }) + + t.Run("with rule_matcher regex filter", func(t *testing.T) { + // Filter with regex team=~plat.* (should match group-3 with team=platform) + matcher := url.QueryEscape(`{"name":"team","value":"plat.*","isRegex":true,"isEqual":true}`) + promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/prometheus/grafana/api/v1/rules?rule_matcher=%s", grafanaListedAddr, matcher) + // nolint:gosec + resp, err := http.Get(promRulesURL) + require.NoError(t, err) + t.Cleanup(func() { + err := resp.Body.Close() + require.NoError(t, err) + }) + + var result apimodels.RuleResponse + err = json.NewDecoder(resp.Body).Decode(&result) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) + + // Should only return group-3 (team=platform matches plat.*) + foundGroups := []string{} + total := 0 + for _, group := range result.Data.RuleGroups { + foundGroups = append(foundGroups, group.Name) + total += len(group.Rules) + } + require.Equal(t, []string{"group-3"}, foundGroups) + require.Equal(t, 2, total) + }) } func TestIntegrationPrometheusRulesFilterByDashboard(t *testing.T) { diff --git a/public/app/features/alerting/unified/api/prometheusApi.ts b/public/app/features/alerting/unified/api/prometheusApi.ts index 3c4fe219dd0..12fa3aae223 100644 --- a/public/app/features/alerting/unified/api/prometheusApi.ts +++ b/public/app/features/alerting/unified/api/prometheusApi.ts @@ -47,6 +47,7 @@ export type GrafanaPromRulesOptions = Omit ({ url: `api/prometheus/grafana/api/v1/rules`, params: { @@ -120,6 +122,7 @@ export const prometheusApi = alertingApi.injectEndpoints({ 'search.rule_name': title, 'search.rule_group': searchGroupName, dashboard_uid: dashboardUid, + rule_matcher: ruleMatchers, }, }), providesTags: (_result, _error, { folderUid, groupName, ruleName }) => { diff --git a/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.test.ts b/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.test.ts index 12a46a87bdd..ab9404d4e52 100644 --- a/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.test.ts +++ b/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.test.ts @@ -1,9 +1,12 @@ import { testWithFeatureToggles } from 'test/test-utils'; +import { config } from '@grafana/runtime'; import { PromAlertingRuleState, PromRuleGroupDTO, PromRuleType } from 'app/types/unified-alerting-dto'; import { mockGrafanaPromAlertingRule, mockPromRecordingRule } from '../../mocks'; import { RuleHealth } from '../../search/rulesSearchParser'; +import { pluginMeta, pluginMetaToPluginConfig } from '../../testSetup/plugins'; +import { SupportedPlugin } from '../../types/pluginBridges'; import { Annotation } from '../../utils/constants'; import { getDatasourceAPIUid } from '../../utils/datasource'; import { getFilter } from '../../utils/search'; @@ -416,19 +419,40 @@ describe('grafana-managed rules', () => { expect(frontendFilter.groupMatches(group)).toBe(true); }); + it('should include ruleMatchers in backend filter when labels are provided', () => { + const { backendFilter } = getGrafanaFilter(getFilter({ labels: ['severity=critical'] })); + + expect(backendFilter.ruleMatchers).toBeDefined(); + expect(backendFilter.ruleMatchers).toHaveLength(1); + expect(backendFilter.ruleMatchers).toEqual([ + '{"name":"severity","value":"critical","isRegex":false,"isEqual":true}', + ]); + }); + it('should still apply other frontend filters', () => { - const rule = mockGrafanaPromAlertingRule({ + // Set up test plugin as installed + config.apps[SupportedPlugin.Slo] = pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Slo]); + + const regularRule = mockGrafanaPromAlertingRule({ name: 'High CPU Usage', labels: { severity: 'critical', team: 'ops' }, alerts: [], }); - // Label filter should still work on frontend - const { frontendFilter } = getGrafanaFilter(getFilter({ labels: ['severity=warning'] })); - expect(frontendFilter.ruleMatches(rule)).toBe(false); + const pluginRule = mockGrafanaPromAlertingRule({ + name: 'Plugin Rule', + labels: { __grafana_origin: `plugin/${SupportedPlugin.Slo}` }, + alerts: [], + }); - const { frontendFilter: frontendFilter2 } = getGrafanaFilter(getFilter({ labels: ['severity=critical'] })); - expect(frontendFilter2.ruleMatches(rule)).toBe(true); + // Plugins filter should still work on frontend + const { frontendFilter } = getGrafanaFilter(getFilter({ plugins: 'hide' })); + + // Non-plugin rules should pass through + expect(frontendFilter.ruleMatches(regularRule)).toBe(true); + + // Plugin-provided rules should be filtered out + expect(frontendFilter.ruleMatches(pluginRule)).toBe(false); }); }); @@ -681,20 +705,7 @@ describe('grafana-managed rules', () => { expect(frontendFilter.groupMatches(group)).toBe(true); }); - it('should still apply always-frontend filters (labels, namespace)', () => { - const rule = mockGrafanaPromAlertingRule({ - name: 'High CPU Usage', - labels: { severity: 'critical' }, - alerts: [], - }); - - // Labels filter should still work - const { frontendFilter: labelFilter } = getGrafanaFilter(getFilter({ labels: ['severity=warning'] })); - expect(labelFilter.ruleMatches(rule)).toBe(false); - - const { frontendFilter: labelFilter2 } = getGrafanaFilter(getFilter({ labels: ['severity=critical'] })); - expect(labelFilter2.ruleMatches(rule)).toBe(true); - + it('should still apply always-frontend filters (namespace)', () => { // Namespace filter should still work const group: PromRuleGroupDTO = { name: 'Test Group', @@ -791,9 +802,13 @@ describe('grafana-managed rules', () => { expect(hasGrafanaClientSideFilters(getFilter({ dataSourceNames: ['prometheus'] }))).toBe(false); }); + it('should return false for labels (handled by backend when feature toggle is enabled)', () => { + expect(hasGrafanaClientSideFilters(getFilter({ labels: ['severity=critical'] }))).toBe(false); + }); + it('should return true for client-side only filters', () => { expect(hasGrafanaClientSideFilters(getFilter({ namespace: 'production' }))).toBe(true); - expect(hasGrafanaClientSideFilters(getFilter({ labels: ['severity=critical'] }))).toBe(true); + expect(hasGrafanaClientSideFilters(getFilter({ plugins: 'hide' }))).toBe(true); }); it('should return false for backend-only filters (state, health, contactPoint)', () => { @@ -816,12 +831,13 @@ describe('grafana-managed rules', () => { expect(hasGrafanaClientSideFilters(getFilter({ ruleHealth: RuleHealth.Ok }))).toBe(false); expect(hasGrafanaClientSideFilters(getFilter({ contactPoint: 'my-contact-point' }))).toBe(false); - // Should return true for: frontend-handled filters + // Should return true for: frontend-handled filters (labels, namespace, plugins) expect(hasGrafanaClientSideFilters(getFilter({ freeFormWords: ['cpu'] }))).toBe(true); expect(hasGrafanaClientSideFilters(getFilter({ ruleName: 'alert' }))).toBe(true); expect(hasGrafanaClientSideFilters(getFilter({ groupName: 'test-group' }))).toBe(true); expect(hasGrafanaClientSideFilters(getFilter({ namespace: 'production' }))).toBe(true); expect(hasGrafanaClientSideFilters(getFilter({ labels: ['severity=critical'] }))).toBe(true); + expect(hasGrafanaClientSideFilters(getFilter({ plugins: 'hide' }))).toBe(true); }); }); @@ -840,12 +856,13 @@ describe('grafana-managed rules', () => { expect(hasGrafanaClientSideFilters(getFilter({ ruleHealth: RuleHealth.Ok }))).toBe(false); expect(hasGrafanaClientSideFilters(getFilter({ contactPoint: 'my-contact-point' }))).toBe(false); - // Should return true for: always-frontend filters only (namespace, labels) + // Should return true for: always-frontend filters only (namespace, plugins) expect(hasGrafanaClientSideFilters(getFilter({ namespace: 'production' }))).toBe(true); - expect(hasGrafanaClientSideFilters(getFilter({ labels: ['severity=critical'] }))).toBe(true); + expect(hasGrafanaClientSideFilters(getFilter({ plugins: 'hide' }))).toBe(true); - // Should return false for: backend-handled dataSourceNames when feature toggles are enabled + // Should return false for: backend-handled filters when both feature toggles are enabled expect(hasGrafanaClientSideFilters(getFilter({ dataSourceNames: ['prometheus'] }))).toBe(false); + expect(hasGrafanaClientSideFilters(getFilter({ labels: ['severity=critical'] }))).toBe(false); }); }); }); diff --git a/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.ts b/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.ts index c0fd9fea4d8..96ee951ee37 100644 --- a/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.ts +++ b/public/app/features/alerting/unified/rule-list/hooks/grafanaFilter.ts @@ -1,8 +1,11 @@ +import { attempt, isError } from 'lodash'; + import { PromRuleDTO, PromRuleGroupDTO } from 'app/types/unified-alerting-dto'; import { GrafanaPromRulesOptions } from '../../api/prometheusApi'; import { shouldUseBackendFilters, shouldUseFullyCompatibleBackendFilters } from '../../featureToggles'; import { RulesFilter } from '../../search/rulesSearchParser'; +import { parseMatcher } from '../../utils/matchers'; import { buildTitleSearch, normalizeFilterState } from './filterNormalization'; import { @@ -75,6 +78,12 @@ export function getGrafanaFilter(filterState: Partial) { hasInvalidDataSourceNames = datasourceUids.length === 0; } + // Convert labels to JSON-encoded matchers for backend filtering + const ruleMatchersBackendFilter: string[] | undefined = + ruleFilterConfig.labels || normalizedFilterState.labels.length === 0 + ? undefined + : labelMatchersToBackendFormat(normalizedFilterState.labels); + const backendFilter: GrafanaPromRulesOptions = { state: normalizedFilterState.ruleState ? [normalizedFilterState.ruleState] : [], health: normalizedFilterState.ruleHealth ? [normalizedFilterState.ruleHealth] : [], @@ -85,6 +94,7 @@ export function getGrafanaFilter(filterState: Partial) { dashboardUid: ruleFilterConfig.dashboardUid ? undefined : normalizedFilterState.dashboardUid, searchGroupName: groupFilterConfig.groupName ? undefined : normalizedFilterState.groupName, datasources: ruleFilterConfig.dataSourceNames ? undefined : datasourceUids, + ruleMatchers: ruleMatchersBackendFilter, }; return { @@ -115,7 +125,7 @@ function buildGrafanaFilterConfigs() { ruleState: null, ruleType: useBackendFilters || useFullyCompatibleBackendFilters ? null : ruleTypeFilter, dataSourceNames: useBackendFilters || useFullyCompatibleBackendFilters ? null : dataSourceNamesFilter, - labels: labelsFilter, + labels: useBackendFilters ? null : labelsFilter, ruleHealth: null, dashboardUid: useBackendFilters || useFullyCompatibleBackendFilters ? null : dashboardUidFilter, plugins: pluginsFilter, @@ -129,3 +139,21 @@ function buildGrafanaFilterConfigs() { return { ruleFilterConfig, groupFilterConfig }; } + +/** + * Converts label matchers to JSON-encoded strings for backend filtering. + * Invalid matchers are logged and filtered out. + */ +function labelMatchersToBackendFormat(labels: string[]): string[] { + return labels.reduce((acc, label) => { + const result = attempt(() => JSON.stringify(parseMatcher(label))); + + if (isError(result)) { + console.warn('Failed to parse label matcher:', label, result); + } else { + acc.push(result); + } + + return acc; + }, []); +} diff --git a/public/app/features/alerting/unified/rule-list/paginationLimits.test.ts b/public/app/features/alerting/unified/rule-list/paginationLimits.test.ts index 2e1db699883..5d6b7c97782 100644 --- a/public/app/features/alerting/unified/rule-list/paginationLimits.test.ts +++ b/public/app/features/alerting/unified/rule-list/paginationLimits.test.ts @@ -74,6 +74,7 @@ describe('paginationLimits', () => { { ruleHealth: RuleHealth.Ok }, { contactPoint: 'slack' }, { dataSourceNames: ['prometheus'] }, + { labels: ['severity=critical'] }, ])( 'should return rule limit for grafana + large limit for datasource when only backend filters are used: %p', (filterState) => { @@ -86,7 +87,6 @@ describe('paginationLimits', () => { it.each>([ { namespace: 'production' }, - { labels: ['severity=critical'] }, { ruleState: PromAlertingRuleState.Firing, namespace: 'production' }, ])('should return large limits for both when frontend filters are used: %p', (filterState) => { const { grafanaManagedLimit, datasourceManagedLimit } = getFilteredRulesLimits(getFilter(filterState)); @@ -157,6 +157,7 @@ describe('paginationLimits', () => { { ruleHealth: RuleHealth.Ok }, { contactPoint: 'slack' }, { dataSourceNames: ['prometheus'] }, + { labels: ['severity=critical'] }, ])( 'should return rule limit for grafana + large limit for datasource when only backend filters are used: %p', (filterState) => { @@ -167,7 +168,7 @@ describe('paginationLimits', () => { } ); - it.each>([{ namespace: 'production' }, { labels: ['severity=critical'] }])( + it.each>([{ namespace: 'production' }])( 'should return large limits for both when frontend filters are used: %p', (filterState) => { const { grafanaManagedLimit, datasourceManagedLimit } = getFilteredRulesLimits(getFilter(filterState)); From 5ecfc79e149b0aeed1e612a82fe522ab2fb717fd Mon Sep 17 00:00:00 2001 From: Daniele Stefano Ferru Date: Tue, 16 Dec 2025 14:37:07 +0100 Subject: [PATCH 150/297] Provisioning: Add Connection resource (#115272) * Provisioning: Add Connection resource * adding some more integration tests * updating openapi snapshot, linting * generating FE code, fixing issue in unit tests * addressing comments * addressing comments * adding more integration tests * fixing rebase issues * removing linting exception * addressing comments: improving validation and tests * adding Connection URL at mutation time, updating tests accordingly * linting --- apps/provisioning/kinds/connection.cue | 73 + apps/provisioning/kinds/manifest.cue | 3 +- .../apis/provisioning/v0alpha1/connections.go | 118 ++ .../pkg/apis/provisioning/v0alpha1/health.go | 26 + .../apis/provisioning/v0alpha1/register.go | 43 + .../pkg/apis/provisioning/v0alpha1/types.go | 25 - .../v0alpha1/zz_generated.deepcopy.go | 177 +++ .../v0alpha1/zz_generated.openapi.go | 309 ++++ ...enerated.openapi_violation_exceptions.list | 3 + apps/provisioning/pkg/connection/mutator.go | 28 + .../pkg/connection/mutator_test.go | 35 + apps/provisioning/pkg/connection/validator.go | 104 ++ .../pkg/connection/validator_test.go | 253 +++ .../v0alpha1/bitbucketconnectionconfig.go | 25 + .../provisioning/v0alpha1/connection.go | 237 +++ .../provisioning/v0alpha1/connectionsecure.go | 47 + .../provisioning/v0alpha1/connectionspec.go | 65 + .../provisioning/v0alpha1/connectionstatus.go | 47 + .../v0alpha1/githubconnectionconfig.go | 34 + .../v0alpha1/gitlabconnectionconfig.go | 25 + .../pkg/generated/applyconfiguration/utils.go | 14 + .../typed/provisioning/v0alpha1/connection.go | 60 + .../v0alpha1/fake/fake_connection.go | 37 + .../v0alpha1/fake/fake_provisioning_client.go | 4 + .../v0alpha1/generated_expansion.go | 2 + .../v0alpha1/provisioning_client.go | 5 + .../informers/externalversions/generic.go | 2 + .../provisioning/v0alpha1/connection.go | 88 ++ .../provisioning/v0alpha1/interface.go | 7 + .../provisioning/v0alpha1/connection.go | 56 + .../v0alpha1/expansion_generated.go | 8 + .../provisioning/v0alpha1/endpoints.gen.ts | 624 ++++++-- .../controller/repository_test.go | 4 + pkg/registry/apis/provisioning/register.go | 44 + .../provisioning.grafana.app-v0alpha1.json | 1374 +++++++++++++++++ .../apis/provisioning/connection_test.go | 413 +++++ pkg/tests/apis/provisioning/helper_test.go | 7 + 37 files changed, 4305 insertions(+), 121 deletions(-) create mode 100644 apps/provisioning/kinds/connection.cue create mode 100644 apps/provisioning/pkg/apis/provisioning/v0alpha1/connections.go create mode 100644 apps/provisioning/pkg/apis/provisioning/v0alpha1/health.go create mode 100644 apps/provisioning/pkg/connection/mutator.go create mode 100644 apps/provisioning/pkg/connection/mutator_test.go create mode 100644 apps/provisioning/pkg/connection/validator.go create mode 100644 apps/provisioning/pkg/connection/validator_test.go create mode 100644 apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/bitbucketconnectionconfig.go create mode 100644 apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connection.go create mode 100644 apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionsecure.go create mode 100644 apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionspec.go create mode 100644 apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionstatus.go create mode 100644 apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/githubconnectionconfig.go create mode 100644 apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/gitlabconnectionconfig.go create mode 100644 apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/connection.go create mode 100644 apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_connection.go create mode 100644 apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/connection.go create mode 100644 apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/connection.go create mode 100644 pkg/tests/apis/provisioning/connection_test.go diff --git a/apps/provisioning/kinds/connection.cue b/apps/provisioning/kinds/connection.cue new file mode 100644 index 00000000000..23af1991c02 --- /dev/null +++ b/apps/provisioning/kinds/connection.cue @@ -0,0 +1,73 @@ +package repository + +connection: { + kind: "Connection" + pluralName: "Connections" + current: "v0alpha1" + validation: { + operations: [ + "CREATE", + "UPDATE", + ] + } + versions: { + "v0alpha1": { + codegen: { + ts: {enabled: false} + go: {enabled: true} + } + schema: { + #GitHubConnectionConfig: { + // App-level information + // GitHub App ID + appID: int + + // Installation-level information + // GitHub App installation ID + installationID: int + } + #BitbucketConnectionConfig: { + // The app clientID + clientID: string + } + #GitlabConnectionConfig: { + // The app clientID + clientID: string + } + #HealthStatus: { + // When not healthy, requests will not be executed + healthy: bool + // When the health was checked last time + checked?: int + // Summary messages (can be shown to users) + // Will only be populated when not healthy + message?: [...string] + } + spec: { + // The connection provider type + type: "github" | "bitbucket" | "gitlab" + // The connection URL + url: *"" | string + // GitHub connection configuration + // Only applicable when provider is "github" + github?: #GitHubConnectionConfig + // Bitbucket connection configuration + // Only applicable when provider is "bitbucket" + bitbucket?: #BitbucketConnectionConfig + // Gitlab connection configuration + // Only applicable when provider is "gitlab" + gitlab?: #GitlabConnectionConfig + } + status: { + // The generation of the spec last time reconciliation ran + observedGeneration?: int + // Connection state + state: "connected" | "disconnected" + // The connection health status + health: #HealthStatus + } + } + } + } +} + diff --git a/apps/provisioning/kinds/manifest.cue b/apps/provisioning/kinds/manifest.cue index 40ffa64d922..d0d751dd62b 100644 --- a/apps/provisioning/kinds/manifest.cue +++ b/apps/provisioning/kinds/manifest.cue @@ -5,5 +5,6 @@ manifest: { groupOverride: "provisioning.grafana.app" kinds: [ repository, + connection ] -} \ No newline at end of file +} diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/connections.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/connections.go new file mode 100644 index 00000000000..228523f598e --- /dev/null +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/connections.go @@ -0,0 +1,118 @@ +package v0alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" +) + +// When this code is changed, make sure to update the code generation. +// As of writing, this can be done via the hack dir in the root of the repo: ./hack/update-codegen.sh provisioning +// If you've opened the generated files in this dir at some point in VSCode, you may also have to re-open them to clear errors. +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type Connection struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ConnectionSpec `json:"spec,omitempty"` + Secure ConnectionSecure `json:"secure,omitzero,omitempty"` + Status ConnectionStatus `json:"status,omitempty"` +} + +type ConnectionSecure struct { + // PrivateKey is the reference to the private key used for GitHub App authentication. + // This value is stored securely and cannot be read back + PrivateKey common.InlineSecureValue `json:"privateKey,omitzero,omitempty"` + + // ClientSecret is the reference to the secret used for other providers authentication, + // and Github on-behalf-of authentication. + // This value is stored securely and cannot be read back + ClientSecret common.InlineSecureValue `json:"clientSecret,omitzero,omitempty"` + + // Token is the reference of the token used to act as the Connection. + // This value is stored securely and cannot be read back + Token common.InlineSecureValue `json:"webhook,omitzero,omitempty"` +} + +func (v ConnectionSecure) IsZero() bool { + return v.PrivateKey.IsZero() && v.Token.IsZero() +} + +type GitHubConnectionConfig struct { + // GitHub App ID + AppID string `json:"appID"` + + // GitHub App installation ID + InstallationID string `json:"installationID"` +} + +type BitbucketConnectionConfig struct { + // App client ID + ClientID string `json:"clientID"` +} + +type GitlabConnectionConfig struct { + // App client ID + ClientID string `json:"clientID"` +} + +// ConnectionType defines the types of Connection providers +// +enum +type ConnectionType string + +// ConnectionType values. +const ( + GithubConnectionType ConnectionType = "github" + GitlabConnectionType ConnectionType = "gitlab" + BitbucketConnectionType ConnectionType = "bitbucket" +) + +type ConnectionSpec struct { + // The connection provider type + Type ConnectionType `json:"type"` + // The connection URL + URL string `json:"url,omitempty"` + + // GitHub connection configuration + // Only applicable when provider is "github" + GitHub *GitHubConnectionConfig `json:"github,omitempty"` + // Bitbucket connection configuration + // Only applicable when provider is "bitbucket" + Bitbucket *BitbucketConnectionConfig `json:"bitbucket,omitempty"` + // Gitlab connection configuration + // Only applicable when provider is "gitlab" + Gitlab *GitlabConnectionConfig `json:"gitlab,omitempty"` +} + +// ConnectionState defines the state of a Connection +// +enum +type ConnectionState string + +// ConnectionState values +const ( + ConnectionStateConnected ConnectionState = "connected" + ConnectionStateDisconnected ConnectionState = "disconnected" +) + +// The status of a Connection. +// This is expected never to be created by a kubectl call or similar, and is expected to rarely (if ever) be edited manually. +type ConnectionStatus struct { + // The generation of the spec last time reconciliation ran + ObservedGeneration int64 `json:"observedGeneration"` + + // Connection state + State ConnectionState `json:"state"` + + // The connection health status + Health HealthStatus `json:"health"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ConnectionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + // +listType=atomic + Items []Connection `json:"items"` +} diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/health.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/health.go new file mode 100644 index 00000000000..1298580c9a9 --- /dev/null +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/health.go @@ -0,0 +1,26 @@ +package v0alpha1 + +// HealthFailureType represents different types of healthcheck failures +// +enum +type HealthFailureType string + +const ( + HealthFailureHook HealthFailureType = "hook" + HealthFailureHealth HealthFailureType = "health" +) + +type HealthStatus struct { + // When not healthy, requests will not be executed + Healthy bool `json:"healthy"` + + // The type of the error + Error HealthFailureType `json:"error,omitempty"` + + // When the health was checked last time + Checked int64 `json:"checked,omitempty"` + + // Summary messages (can be shown to users) + // Will only be populated when not healthy + // +listType=atomic + Message []string `json:"message,omitempty"` +} diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/register.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/register.go index 777484828f6..f06798c0ddd 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/register.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/register.go @@ -115,6 +115,47 @@ var HistoricJobResourceInfo = utils.NewResourceInfo(GROUP, VERSION, }, }) +var ConnectionResourceInfo = utils.NewResourceInfo(GROUP, VERSION, + "connections", "connection", "Connection", + func() runtime.Object { return &Connection{} }, // newObj + func() runtime.Object { return &ConnectionList{} }, // newList + utils.TableColumns{ // Returned by `kubectl get`. Doesn't affect disk storage. + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Created At", Type: "date"}, + {Name: "Type", Type: "string"}, + {Name: "AppID", Type: "string"}, + {Name: "InstallationID", Type: "string"}, + {Name: "ClientID", Type: "string"}, + }, + Reader: func(obj any) ([]interface{}, error) { + m, ok := obj.(*Connection) + if !ok { + return nil, errors.New("expected Repository") + } + + var appID, installationID, clientID string + switch m.Spec.Type { + case GithubConnectionType: + appID = m.Spec.GitHub.AppID + installationID = m.Spec.GitHub.InstallationID + case BitbucketConnectionType: + clientID = m.Spec.Bitbucket.ClientID + case GitlabConnectionType: + clientID = m.Spec.Gitlab.ClientID + } + + return []interface{}{ + m.Name, + m.CreationTimestamp.UTC().Format(time.RFC3339), + m.Spec.Type, + appID, + installationID, + clientID, + }, nil + }, + }) + var ( // SchemeGroupVersion is group version used to register these objects SchemeGroupVersion = schema.GroupVersion{Group: GROUP, Version: VERSION} @@ -154,6 +195,8 @@ func AddKnownTypes(gv schema.GroupVersion, scheme *runtime.Scheme) error { &RefList{}, &HistoricJob{}, &HistoricJobList{}, + &Connection{}, + &ConnectionList{}, ) return nil } diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go index dff7c3813e5..a4c4f0ef643 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go @@ -315,31 +315,6 @@ type RepositoryStatus struct { DeleteError string `json:"deleteError,omitempty"` } -// HealthFailureType represents different types of repository failures -// +enum -type HealthFailureType string - -const ( - HealthFailureHook HealthFailureType = "hook" - HealthFailureHealth HealthFailureType = "health" -) - -type HealthStatus struct { - // When not healthy, requests will not be executed - Healthy bool `json:"healthy"` - - // The type of the error - Error HealthFailureType `json:"error,omitempty"` - - // When the health was checked last time - Checked int64 `json:"checked,omitempty"` - - // Summary messages (can be shown to users) - // Will only be populated when not healthy - // +listType=atomic - Message []string `json:"message,omitempty"` -} - type SyncStatus struct { // pending, running, success, error State JobState `json:"state"` diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go index 4bf4f7674ff..6ca50fdd277 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go @@ -27,6 +27,22 @@ func (in *Author) DeepCopy() *Author { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BitbucketConnectionConfig) DeepCopyInto(out *BitbucketConnectionConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BitbucketConnectionConfig. +func (in *BitbucketConnectionConfig) DeepCopy() *BitbucketConnectionConfig { + if in == nil { + return nil + } + out := new(BitbucketConnectionConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BitbucketRepositoryConfig) DeepCopyInto(out *BitbucketRepositoryConfig) { *out = *in @@ -43,6 +59,135 @@ func (in *BitbucketRepositoryConfig) DeepCopy() *BitbucketRepositoryConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Connection) DeepCopyInto(out *Connection) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Secure = in.Secure + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Connection. +func (in *Connection) DeepCopy() *Connection { + if in == nil { + return nil + } + out := new(Connection) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Connection) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConnectionList) DeepCopyInto(out *ConnectionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Connection, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionList. +func (in *ConnectionList) DeepCopy() *ConnectionList { + if in == nil { + return nil + } + out := new(ConnectionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ConnectionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConnectionSecure) DeepCopyInto(out *ConnectionSecure) { + *out = *in + out.PrivateKey = in.PrivateKey + out.ClientSecret = in.ClientSecret + out.Token = in.Token + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionSecure. +func (in *ConnectionSecure) DeepCopy() *ConnectionSecure { + if in == nil { + return nil + } + out := new(ConnectionSecure) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConnectionSpec) DeepCopyInto(out *ConnectionSpec) { + *out = *in + if in.GitHub != nil { + in, out := &in.GitHub, &out.GitHub + *out = new(GitHubConnectionConfig) + **out = **in + } + if in.Bitbucket != nil { + in, out := &in.Bitbucket, &out.Bitbucket + *out = new(BitbucketConnectionConfig) + **out = **in + } + if in.Gitlab != nil { + in, out := &in.Gitlab, &out.Gitlab + *out = new(GitlabConnectionConfig) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionSpec. +func (in *ConnectionSpec) DeepCopy() *ConnectionSpec { + if in == nil { + return nil + } + out := new(ConnectionSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConnectionStatus) DeepCopyInto(out *ConnectionStatus) { + *out = *in + in.Health.DeepCopyInto(&out.Health) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionStatus. +func (in *ConnectionStatus) DeepCopy() *ConnectionStatus { + if in == nil { + return nil + } + out := new(ConnectionStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DeleteJobOptions) DeepCopyInto(out *DeleteJobOptions) { *out = *in @@ -148,6 +293,22 @@ func (in *FileList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitHubConnectionConfig) DeepCopyInto(out *GitHubConnectionConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitHubConnectionConfig. +func (in *GitHubConnectionConfig) DeepCopy() *GitHubConnectionConfig { + if in == nil { + return nil + } + out := new(GitHubConnectionConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GitHubRepositoryConfig) DeepCopyInto(out *GitHubRepositoryConfig) { *out = *in @@ -196,6 +357,22 @@ func (in *GitRepositoryConfig) DeepCopy() *GitRepositoryConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitlabConnectionConfig) DeepCopyInto(out *GitlabConnectionConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitlabConnectionConfig. +func (in *GitlabConnectionConfig) DeepCopy() *GitlabConnectionConfig { + if in == nil { + return nil + } + out := new(GitlabConnectionConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HealthStatus) DeepCopyInto(out *HealthStatus) { *out = *in diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go index 933525eca0b..0e1a224d457 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go @@ -15,15 +15,23 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Author": schema_pkg_apis_provisioning_v0alpha1_Author(ref), + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.BitbucketConnectionConfig": schema_pkg_apis_provisioning_v0alpha1_BitbucketConnectionConfig(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.BitbucketRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_BitbucketRepositoryConfig(ref), + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Connection": schema_pkg_apis_provisioning_v0alpha1_Connection(ref), + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionList": schema_pkg_apis_provisioning_v0alpha1_ConnectionList(ref), + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionSecure": schema_pkg_apis_provisioning_v0alpha1_ConnectionSecure(ref), + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionSpec": schema_pkg_apis_provisioning_v0alpha1_ConnectionSpec(ref), + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionStatus": schema_pkg_apis_provisioning_v0alpha1_ConnectionStatus(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.DeleteJobOptions": schema_pkg_apis_provisioning_v0alpha1_DeleteJobOptions(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ErrorDetails": schema_pkg_apis_provisioning_v0alpha1_ErrorDetails(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ExportJobOptions": schema_pkg_apis_provisioning_v0alpha1_ExportJobOptions(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.FileItem": schema_pkg_apis_provisioning_v0alpha1_FileItem(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.FileList": schema_pkg_apis_provisioning_v0alpha1_FileList(ref), + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitHubConnectionConfig": schema_pkg_apis_provisioning_v0alpha1_GitHubConnectionConfig(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitHubRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_GitHubRepositoryConfig(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitLabRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_GitLabRepositoryConfig(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_GitRepositoryConfig(ref), + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitlabConnectionConfig": schema_pkg_apis_provisioning_v0alpha1_GitlabConnectionConfig(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HealthStatus": schema_pkg_apis_provisioning_v0alpha1_HealthStatus(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HistoricJob": schema_pkg_apis_provisioning_v0alpha1_HistoricJob(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HistoricJobList": schema_pkg_apis_provisioning_v0alpha1_HistoricJobList(ref), @@ -100,6 +108,27 @@ func schema_pkg_apis_provisioning_v0alpha1_Author(ref common.ReferenceCallback) } } +func schema_pkg_apis_provisioning_v0alpha1_BitbucketConnectionConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "clientID": { + SchemaProps: spec.SchemaProps{ + Description: "App client ID", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"clientID"}, + }, + }, + } +} + func schema_pkg_apis_provisioning_v0alpha1_BitbucketRepositoryConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -142,6 +171,236 @@ func schema_pkg_apis_provisioning_v0alpha1_BitbucketRepositoryConfig(ref common. } } +func schema_pkg_apis_provisioning_v0alpha1_Connection(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "When this code is changed, make sure to update the code generation. As of writing, this can be done via the hack dir in the root of the repo: ./hack/update-codegen.sh provisioning If you've opened the generated files in this dir at some point in VSCode, you may also have to re-open them to clear errors.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionSpec"), + }, + }, + "secure": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionSecure"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionSecure", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionSpec", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_provisioning_v0alpha1_ConnectionList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Connection"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Connection", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_provisioning_v0alpha1_ConnectionSecure(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "privateKey": { + SchemaProps: spec.SchemaProps{ + Description: "PrivateKey is the reference to the private key used for GitHub App authentication. This value is stored securely and cannot be read back", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue"), + }, + }, + "clientSecret": { + SchemaProps: spec.SchemaProps{ + Description: "ClientSecret is the reference to the secret used for other providers authentication, and Github on-behalf-of authentication. This value is stored securely and cannot be read back", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue"), + }, + }, + "webhook": { + SchemaProps: spec.SchemaProps{ + Description: "Token is the reference of the token used to act as the Connection. This value is stored securely and cannot be read back", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue"}, + } +} + +func schema_pkg_apis_provisioning_v0alpha1_ConnectionSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "The connection provider type\n\nPossible enum values:\n - `\"bitbucket\"`\n - `\"github\"`\n - `\"gitlab\"`", + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"bitbucket", "github", "gitlab"}, + }, + }, + "url": { + SchemaProps: spec.SchemaProps{ + Description: "The connection URL", + Type: []string{"string"}, + Format: "", + }, + }, + "github": { + SchemaProps: spec.SchemaProps{ + Description: "GitHub connection configuration Only applicable when provider is \"github\"", + Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitHubConnectionConfig"), + }, + }, + "bitbucket": { + SchemaProps: spec.SchemaProps{ + Description: "Bitbucket connection configuration Only applicable when provider is \"bitbucket\"", + Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.BitbucketConnectionConfig"), + }, + }, + "gitlab": { + SchemaProps: spec.SchemaProps{ + Description: "Gitlab connection configuration Only applicable when provider is \"gitlab\"", + Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitlabConnectionConfig"), + }, + }, + }, + Required: []string{"type"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.BitbucketConnectionConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitHubConnectionConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitlabConnectionConfig"}, + } +} + +func schema_pkg_apis_provisioning_v0alpha1_ConnectionStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "The status of a Connection. This is expected never to be created by a kubectl call or similar, and is expected to rarely (if ever) be edited manually.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "observedGeneration": { + SchemaProps: spec.SchemaProps{ + Description: "The generation of the spec last time reconciliation ran", + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "state": { + SchemaProps: spec.SchemaProps{ + Description: "Connection state\n\nPossible enum values:\n - `\"connected\"`\n - `\"disconnected\"`", + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"connected", "disconnected"}, + }, + }, + "health": { + SchemaProps: spec.SchemaProps{ + Description: "The connection health status", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HealthStatus"), + }, + }, + }, + Required: []string{"observedGeneration", "state", "health"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.HealthStatus"}, + } +} + func schema_pkg_apis_provisioning_v0alpha1_DeleteJobOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -362,6 +621,35 @@ func schema_pkg_apis_provisioning_v0alpha1_FileList(ref common.ReferenceCallback } } +func schema_pkg_apis_provisioning_v0alpha1_GitHubConnectionConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "appID": { + SchemaProps: spec.SchemaProps{ + Description: "GitHub App ID", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "installationID": { + SchemaProps: spec.SchemaProps{ + Description: "GitHub App installation ID", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"appID", "installationID"}, + }, + }, + } +} + func schema_pkg_apis_provisioning_v0alpha1_GitHubRepositoryConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -481,6 +769,27 @@ func schema_pkg_apis_provisioning_v0alpha1_GitRepositoryConfig(ref common.Refere } } +func schema_pkg_apis_provisioning_v0alpha1_GitlabConnectionConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "clientID": { + SchemaProps: spec.SchemaProps{ + Description: "App client ID", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"clientID"}, + }, + }, + } +} + func schema_pkg_apis_provisioning_v0alpha1_HealthStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list index 53071a05ec8..b9504855b80 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -1,3 +1,4 @@ +API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ConnectionList,Items API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,DeleteJobOptions,Paths API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,DeleteJobOptions,Resources API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,FileList,Items @@ -20,6 +21,8 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioni API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ResourceList,Items API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,TestResults,Errors API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,WebhookStatus,SubscribedEvents +API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ConnectionSecure,Token +API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ConnectionSpec,GitHub API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobSpec,PullRequest API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobStatus,URLs API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ManagerStats,Identity diff --git a/apps/provisioning/pkg/connection/mutator.go b/apps/provisioning/pkg/connection/mutator.go new file mode 100644 index 00000000000..30291669905 --- /dev/null +++ b/apps/provisioning/pkg/connection/mutator.go @@ -0,0 +1,28 @@ +package connection + +import ( + "fmt" + + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +const ( + githubInstallationURL = "https://github.com/settings/installations" +) + +func MutateConnection(connection *provisioning.Connection) error { + switch connection.Spec.Type { + case provisioning.GithubConnectionType: + // Do nothing in case spec.Github is nil. + // If this field is required, we should fail at validation time. + if connection.Spec.GitHub == nil { + return nil + } + + connection.Spec.URL = fmt.Sprintf("%s/%s", githubInstallationURL, connection.Spec.GitHub.InstallationID) + return nil + default: + // TODO: we need to setup the URL for bitbucket and gitlab. + return nil + } +} diff --git a/apps/provisioning/pkg/connection/mutator_test.go b/apps/provisioning/pkg/connection/mutator_test.go new file mode 100644 index 00000000000..a25aabd10a1 --- /dev/null +++ b/apps/provisioning/pkg/connection/mutator_test.go @@ -0,0 +1,35 @@ +package connection_test + +import ( + "testing" + + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + "github.com/grafana/grafana/apps/provisioning/pkg/connection" + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestMutateConnection(t *testing.T) { + t.Run("should add URL to Github connection", func(t *testing.T) { + c := &provisioning.Connection{ + ObjectMeta: metav1.ObjectMeta{Name: "test-connection"}, + Spec: provisioning.ConnectionSpec{ + Type: provisioning.GithubConnectionType, + GitHub: &provisioning.GitHubConnectionConfig{ + AppID: "123", + InstallationID: "456", + }, + }, + Secure: provisioning.ConnectionSecure{ + PrivateKey: common.InlineSecureValue{ + Name: "test-private-key", + }, + }, + } + + require.NoError(t, connection.MutateConnection(c)) + assert.Equal(t, "https://github.com/settings/installations/456", c.Spec.URL) + }) +} diff --git a/apps/provisioning/pkg/connection/validator.go b/apps/provisioning/pkg/connection/validator.go new file mode 100644 index 00000000000..c2537e3af2f --- /dev/null +++ b/apps/provisioning/pkg/connection/validator.go @@ -0,0 +1,104 @@ +package connection + +import ( + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func ValidateConnection(connection *provisioning.Connection) error { + list := field.ErrorList{} + + if connection.Spec.Type == "" { + list = append(list, field.Required(field.NewPath("spec", "type"), "type must be specified")) + } + + switch connection.Spec.Type { + case provisioning.GithubConnectionType: + list = append(list, validateGithubConnection(connection)...) + case provisioning.BitbucketConnectionType: + list = append(list, validateBitbucketConnection(connection)...) + case provisioning.GitlabConnectionType: + list = append(list, validateGitlabConnection(connection)...) + default: + list = append( + list, field.NotSupported( + field.NewPath("spec", "type"), + connection.Spec.Type, + []provisioning.ConnectionType{ + provisioning.GithubConnectionType, + provisioning.BitbucketConnectionType, + provisioning.GitlabConnectionType, + }), + ) + } + + return toError(connection.GetName(), list) +} + +func validateGithubConnection(connection *provisioning.Connection) field.ErrorList { + list := field.ErrorList{} + + if connection.Spec.GitHub == nil { + list = append( + list, field.Required(field.NewPath("spec", "github"), "github info must be specified for GitHub connection"), + ) + } + + if connection.Secure.PrivateKey.IsZero() { + list = append(list, field.Required(field.NewPath("secure", "privateKey"), "privateKey must be specified for GitHub connection")) + } + if !connection.Secure.ClientSecret.IsZero() { + list = append(list, field.Forbidden(field.NewPath("secure", "clientSecret"), "clientSecret is forbidden in GitHub connection")) + } + + return list +} + +func validateBitbucketConnection(connection *provisioning.Connection) field.ErrorList { + list := field.ErrorList{} + + if connection.Spec.Bitbucket == nil { + list = append( + list, field.Required(field.NewPath("spec", "bitbucket"), "bitbucket info must be specified in Bitbucket connection"), + ) + } + if connection.Secure.ClientSecret.IsZero() { + list = append(list, field.Required(field.NewPath("secure", "clientSecret"), "clientSecret must be specified for Bitbucket connection")) + } + if !connection.Secure.PrivateKey.IsZero() { + list = append(list, field.Forbidden(field.NewPath("secure", "privateKey"), "privateKey is forbidden in Bitbucket connection")) + } + + return list +} + +func validateGitlabConnection(connection *provisioning.Connection) field.ErrorList { + list := field.ErrorList{} + + if connection.Spec.Gitlab == nil { + list = append( + list, field.Required(field.NewPath("spec", "gitlab"), "gitlab info must be specified in Gitlab connection"), + ) + } + if connection.Secure.ClientSecret.IsZero() { + list = append(list, field.Required(field.NewPath("secure", "clientSecret"), "clientSecret must be specified for Gitlab connection")) + } + if !connection.Secure.PrivateKey.IsZero() { + list = append(list, field.Forbidden(field.NewPath("secure", "privateKey"), "privateKey is forbidden in Gitlab connection")) + } + + return list +} + +// toError converts a field.ErrorList to an error, returning nil if the list is empty +func toError(name string, list field.ErrorList) error { + if len(list) == 0 { + return nil + } + return apierrors.NewInvalid( + provisioning.ConnectionResourceInfo.GroupVersionKind().GroupKind(), + name, + list, + ) +} diff --git a/apps/provisioning/pkg/connection/validator_test.go b/apps/provisioning/pkg/connection/validator_test.go new file mode 100644 index 00000000000..23d4b01b800 --- /dev/null +++ b/apps/provisioning/pkg/connection/validator_test.go @@ -0,0 +1,253 @@ +package connection_test + +import ( + "testing" + + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + "github.com/grafana/grafana/apps/provisioning/pkg/connection" + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestValidateConnection(t *testing.T) { + tests := []struct { + name string + connection *provisioning.Connection + wantErr bool + errMsg string + }{ + { + name: "empty type returns error", + connection: &provisioning.Connection{ + ObjectMeta: metav1.ObjectMeta{Name: "test-connection"}, + Spec: provisioning.ConnectionSpec{}, + }, + wantErr: true, + errMsg: "spec.type", + }, + { + name: "invalid type returns error", + connection: &provisioning.Connection{ + ObjectMeta: metav1.ObjectMeta{Name: "test-connection"}, + Spec: provisioning.ConnectionSpec{ + Type: "invalid", + }, + }, + wantErr: true, + errMsg: "spec.type", + }, + { + name: "github type without github config returns error", + connection: &provisioning.Connection{ + ObjectMeta: metav1.ObjectMeta{Name: "test-connection"}, + Spec: provisioning.ConnectionSpec{ + Type: provisioning.GithubConnectionType, + }, + }, + wantErr: true, + errMsg: "spec.github", + }, + { + name: "github type without private key returns error", + connection: &provisioning.Connection{ + ObjectMeta: metav1.ObjectMeta{Name: "test-connection"}, + Spec: provisioning.ConnectionSpec{ + Type: provisioning.GithubConnectionType, + GitHub: &provisioning.GitHubConnectionConfig{ + AppID: "123", + InstallationID: "456", + }, + }, + }, + wantErr: true, + errMsg: "secure.privateKey", + }, + { + name: "github type with client secret returns error", + connection: &provisioning.Connection{ + ObjectMeta: metav1.ObjectMeta{Name: "test-connection"}, + Spec: provisioning.ConnectionSpec{ + Type: provisioning.GithubConnectionType, + GitHub: &provisioning.GitHubConnectionConfig{ + AppID: "123", + InstallationID: "456", + }, + }, + Secure: provisioning.ConnectionSecure{ + PrivateKey: common.InlineSecureValue{ + Name: "test-private-key", + }, + ClientSecret: common.InlineSecureValue{ + Name: "test-client-secret", + }, + }, + }, + wantErr: true, + errMsg: "secure.clientSecret", + }, + { + name: "github type with github config is valid", + connection: &provisioning.Connection{ + ObjectMeta: metav1.ObjectMeta{Name: "test-connection"}, + Spec: provisioning.ConnectionSpec{ + Type: provisioning.GithubConnectionType, + GitHub: &provisioning.GitHubConnectionConfig{ + AppID: "123", + InstallationID: "456", + }, + }, + Secure: provisioning.ConnectionSecure{ + PrivateKey: common.InlineSecureValue{ + Name: "test-private-key", + }, + }, + }, + wantErr: false, + }, + { + name: "bitbucket type without bitbucket config returns error", + connection: &provisioning.Connection{ + ObjectMeta: metav1.ObjectMeta{Name: "test-connection"}, + Spec: provisioning.ConnectionSpec{ + Type: provisioning.BitbucketConnectionType, + }, + }, + wantErr: true, + errMsg: "spec.bitbucket", + }, + { + name: "bitbucket type without client secret returns error", + connection: &provisioning.Connection{ + ObjectMeta: metav1.ObjectMeta{Name: "test-connection"}, + Spec: provisioning.ConnectionSpec{ + Type: provisioning.BitbucketConnectionType, + Bitbucket: &provisioning.BitbucketConnectionConfig{ + ClientID: "client-123", + }, + }, + }, + wantErr: true, + errMsg: "secure.clientSecret", + }, + { + name: "bitbucket type with private key returns error", + connection: &provisioning.Connection{ + ObjectMeta: metav1.ObjectMeta{Name: "test-connection"}, + Spec: provisioning.ConnectionSpec{ + Type: provisioning.BitbucketConnectionType, + Bitbucket: &provisioning.BitbucketConnectionConfig{ + ClientID: "client-123", + }, + }, + Secure: provisioning.ConnectionSecure{ + PrivateKey: common.InlineSecureValue{ + Name: "test-private-key", + }, + ClientSecret: common.InlineSecureValue{ + Name: "test-client-secret", + }, + }, + }, + wantErr: true, + errMsg: "secure.privateKey", + }, + { + name: "bitbucket type with bitbucket config is valid", + connection: &provisioning.Connection{ + ObjectMeta: metav1.ObjectMeta{Name: "test-connection"}, + Spec: provisioning.ConnectionSpec{ + Type: provisioning.BitbucketConnectionType, + Bitbucket: &provisioning.BitbucketConnectionConfig{ + ClientID: "client-123", + }, + }, + Secure: provisioning.ConnectionSecure{ + ClientSecret: common.InlineSecureValue{ + Name: "test-client-secret", + }, + }, + }, + wantErr: false, + }, + { + name: "gitlab type without gitlab config returns error", + connection: &provisioning.Connection{ + ObjectMeta: metav1.ObjectMeta{Name: "test-connection"}, + Spec: provisioning.ConnectionSpec{ + Type: provisioning.GitlabConnectionType, + }, + }, + wantErr: true, + errMsg: "spec.gitlab", + }, + { + name: "gitlab type without client secret returns error", + connection: &provisioning.Connection{ + ObjectMeta: metav1.ObjectMeta{Name: "test-connection"}, + Spec: provisioning.ConnectionSpec{ + Type: provisioning.GitlabConnectionType, + Gitlab: &provisioning.GitlabConnectionConfig{ + ClientID: "client-456", + }, + }, + }, + wantErr: true, + errMsg: "secure.clientSecret", + }, + { + name: "gitlab type with private key returns error", + connection: &provisioning.Connection{ + ObjectMeta: metav1.ObjectMeta{Name: "test-connection"}, + Spec: provisioning.ConnectionSpec{ + Type: provisioning.GitlabConnectionType, + Gitlab: &provisioning.GitlabConnectionConfig{ + ClientID: "client-456", + }, + }, + Secure: provisioning.ConnectionSecure{ + PrivateKey: common.InlineSecureValue{ + Name: "test-private-key", + }, + ClientSecret: common.InlineSecureValue{ + Name: "test-client-secret", + }, + }, + }, + wantErr: true, + errMsg: "secure.privateKey", + }, + { + name: "gitlab type with gitlab config is valid", + connection: &provisioning.Connection{ + ObjectMeta: metav1.ObjectMeta{Name: "test-connection"}, + Spec: provisioning.ConnectionSpec{ + Type: provisioning.GitlabConnectionType, + Gitlab: &provisioning.GitlabConnectionConfig{ + ClientID: "client-456", + }, + }, + Secure: provisioning.ConnectionSecure{ + ClientSecret: common.InlineSecureValue{ + Name: "test-client-secret", + }, + }, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := connection.ValidateConnection(tt.connection) + if tt.wantErr { + assert.Error(t, err) + if tt.errMsg != "" { + assert.Contains(t, err.Error(), tt.errMsg) + } + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/bitbucketconnectionconfig.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/bitbucketconnectionconfig.go new file mode 100644 index 00000000000..9a3604afe71 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/bitbucketconnectionconfig.go @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +// BitbucketConnectionConfigApplyConfiguration represents a declarative configuration of the BitbucketConnectionConfig type for use +// with apply. +type BitbucketConnectionConfigApplyConfiguration struct { + ClientID *string `json:"clientID,omitempty"` +} + +// BitbucketConnectionConfigApplyConfiguration constructs a declarative configuration of the BitbucketConnectionConfig type for use with +// apply. +func BitbucketConnectionConfig() *BitbucketConnectionConfigApplyConfiguration { + return &BitbucketConnectionConfigApplyConfiguration{} +} + +// WithClientID sets the ClientID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ClientID field is set to the value of the last call. +func (b *BitbucketConnectionConfigApplyConfiguration) WithClientID(value string) *BitbucketConnectionConfigApplyConfiguration { + b.ClientID = &value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connection.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connection.go new file mode 100644 index 00000000000..a7a56c62a14 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connection.go @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// ConnectionApplyConfiguration represents a declarative configuration of the Connection type for use +// with apply. +type ConnectionApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *ConnectionSpecApplyConfiguration `json:"spec,omitempty"` + Secure *ConnectionSecureApplyConfiguration `json:"secure,omitempty"` + Status *ConnectionStatusApplyConfiguration `json:"status,omitempty"` +} + +// Connection constructs a declarative configuration of the Connection type for use with +// apply. +func Connection(name, namespace string) *ConnectionApplyConfiguration { + b := &ConnectionApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("Connection") + b.WithAPIVersion("provisioning.grafana.app/v0alpha1") + return b +} +func (b ConnectionApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *ConnectionApplyConfiguration) WithKind(value string) *ConnectionApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *ConnectionApplyConfiguration) WithAPIVersion(value string) *ConnectionApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *ConnectionApplyConfiguration) WithName(value string) *ConnectionApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *ConnectionApplyConfiguration) WithGenerateName(value string) *ConnectionApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *ConnectionApplyConfiguration) WithNamespace(value string) *ConnectionApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *ConnectionApplyConfiguration) WithUID(value types.UID) *ConnectionApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *ConnectionApplyConfiguration) WithResourceVersion(value string) *ConnectionApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *ConnectionApplyConfiguration) WithGeneration(value int64) *ConnectionApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *ConnectionApplyConfiguration) WithCreationTimestamp(value metav1.Time) *ConnectionApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *ConnectionApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *ConnectionApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *ConnectionApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *ConnectionApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *ConnectionApplyConfiguration) WithLabels(entries map[string]string) *ConnectionApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *ConnectionApplyConfiguration) WithAnnotations(entries map[string]string) *ConnectionApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *ConnectionApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *ConnectionApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *ConnectionApplyConfiguration) WithFinalizers(values ...string) *ConnectionApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *ConnectionApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *ConnectionApplyConfiguration) WithSpec(value *ConnectionSpecApplyConfiguration) *ConnectionApplyConfiguration { + b.Spec = value + return b +} + +// WithSecure sets the Secure field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Secure field is set to the value of the last call. +func (b *ConnectionApplyConfiguration) WithSecure(value *ConnectionSecureApplyConfiguration) *ConnectionApplyConfiguration { + b.Secure = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *ConnectionApplyConfiguration) WithStatus(value *ConnectionStatusApplyConfiguration) *ConnectionApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *ConnectionApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *ConnectionApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *ConnectionApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *ConnectionApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionsecure.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionsecure.go new file mode 100644 index 00000000000..8ac26b192c9 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionsecure.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + commonv0alpha1 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" +) + +// ConnectionSecureApplyConfiguration represents a declarative configuration of the ConnectionSecure type for use +// with apply. +type ConnectionSecureApplyConfiguration struct { + PrivateKey *commonv0alpha1.InlineSecureValue `json:"privateKey,omitempty"` + ClientSecret *commonv0alpha1.InlineSecureValue `json:"clientSecret,omitempty"` + Token *commonv0alpha1.InlineSecureValue `json:"webhook,omitempty"` +} + +// ConnectionSecureApplyConfiguration constructs a declarative configuration of the ConnectionSecure type for use with +// apply. +func ConnectionSecure() *ConnectionSecureApplyConfiguration { + return &ConnectionSecureApplyConfiguration{} +} + +// WithPrivateKey sets the PrivateKey field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PrivateKey field is set to the value of the last call. +func (b *ConnectionSecureApplyConfiguration) WithPrivateKey(value commonv0alpha1.InlineSecureValue) *ConnectionSecureApplyConfiguration { + b.PrivateKey = &value + return b +} + +// WithClientSecret sets the ClientSecret field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ClientSecret field is set to the value of the last call. +func (b *ConnectionSecureApplyConfiguration) WithClientSecret(value commonv0alpha1.InlineSecureValue) *ConnectionSecureApplyConfiguration { + b.ClientSecret = &value + return b +} + +// WithToken sets the Token field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Token field is set to the value of the last call. +func (b *ConnectionSecureApplyConfiguration) WithToken(value commonv0alpha1.InlineSecureValue) *ConnectionSecureApplyConfiguration { + b.Token = &value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionspec.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionspec.go new file mode 100644 index 00000000000..1b55b832ae1 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionspec.go @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +// ConnectionSpecApplyConfiguration represents a declarative configuration of the ConnectionSpec type for use +// with apply. +type ConnectionSpecApplyConfiguration struct { + Type *provisioningv0alpha1.ConnectionType `json:"type,omitempty"` + URL *string `json:"url,omitempty"` + GitHub *GitHubConnectionConfigApplyConfiguration `json:"github,omitempty"` + Bitbucket *BitbucketConnectionConfigApplyConfiguration `json:"bitbucket,omitempty"` + Gitlab *GitlabConnectionConfigApplyConfiguration `json:"gitlab,omitempty"` +} + +// ConnectionSpecApplyConfiguration constructs a declarative configuration of the ConnectionSpec type for use with +// apply. +func ConnectionSpec() *ConnectionSpecApplyConfiguration { + return &ConnectionSpecApplyConfiguration{} +} + +// WithType sets the Type field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Type field is set to the value of the last call. +func (b *ConnectionSpecApplyConfiguration) WithType(value provisioningv0alpha1.ConnectionType) *ConnectionSpecApplyConfiguration { + b.Type = &value + return b +} + +// WithURL sets the URL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the URL field is set to the value of the last call. +func (b *ConnectionSpecApplyConfiguration) WithURL(value string) *ConnectionSpecApplyConfiguration { + b.URL = &value + return b +} + +// WithGitHub sets the GitHub field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GitHub field is set to the value of the last call. +func (b *ConnectionSpecApplyConfiguration) WithGitHub(value *GitHubConnectionConfigApplyConfiguration) *ConnectionSpecApplyConfiguration { + b.GitHub = value + return b +} + +// WithBitbucket sets the Bitbucket field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Bitbucket field is set to the value of the last call. +func (b *ConnectionSpecApplyConfiguration) WithBitbucket(value *BitbucketConnectionConfigApplyConfiguration) *ConnectionSpecApplyConfiguration { + b.Bitbucket = value + return b +} + +// WithGitlab sets the Gitlab field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Gitlab field is set to the value of the last call. +func (b *ConnectionSpecApplyConfiguration) WithGitlab(value *GitlabConnectionConfigApplyConfiguration) *ConnectionSpecApplyConfiguration { + b.Gitlab = value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionstatus.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionstatus.go new file mode 100644 index 00000000000..b9d510aac91 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectionstatus.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +// ConnectionStatusApplyConfiguration represents a declarative configuration of the ConnectionStatus type for use +// with apply. +type ConnectionStatusApplyConfiguration struct { + ObservedGeneration *int64 `json:"observedGeneration,omitempty"` + State *provisioningv0alpha1.ConnectionState `json:"state,omitempty"` + Health *HealthStatusApplyConfiguration `json:"health,omitempty"` +} + +// ConnectionStatusApplyConfiguration constructs a declarative configuration of the ConnectionStatus type for use with +// apply. +func ConnectionStatus() *ConnectionStatusApplyConfiguration { + return &ConnectionStatusApplyConfiguration{} +} + +// WithObservedGeneration sets the ObservedGeneration field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ObservedGeneration field is set to the value of the last call. +func (b *ConnectionStatusApplyConfiguration) WithObservedGeneration(value int64) *ConnectionStatusApplyConfiguration { + b.ObservedGeneration = &value + return b +} + +// WithState sets the State field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the State field is set to the value of the last call. +func (b *ConnectionStatusApplyConfiguration) WithState(value provisioningv0alpha1.ConnectionState) *ConnectionStatusApplyConfiguration { + b.State = &value + return b +} + +// WithHealth sets the Health field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Health field is set to the value of the last call. +func (b *ConnectionStatusApplyConfiguration) WithHealth(value *HealthStatusApplyConfiguration) *ConnectionStatusApplyConfiguration { + b.Health = value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/githubconnectionconfig.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/githubconnectionconfig.go new file mode 100644 index 00000000000..c2f2a8b291b --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/githubconnectionconfig.go @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +// GitHubConnectionConfigApplyConfiguration represents a declarative configuration of the GitHubConnectionConfig type for use +// with apply. +type GitHubConnectionConfigApplyConfiguration struct { + AppID *string `json:"appID,omitempty"` + InstallationID *string `json:"installationID,omitempty"` +} + +// GitHubConnectionConfigApplyConfiguration constructs a declarative configuration of the GitHubConnectionConfig type for use with +// apply. +func GitHubConnectionConfig() *GitHubConnectionConfigApplyConfiguration { + return &GitHubConnectionConfigApplyConfiguration{} +} + +// WithAppID sets the AppID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AppID field is set to the value of the last call. +func (b *GitHubConnectionConfigApplyConfiguration) WithAppID(value string) *GitHubConnectionConfigApplyConfiguration { + b.AppID = &value + return b +} + +// WithInstallationID sets the InstallationID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the InstallationID field is set to the value of the last call. +func (b *GitHubConnectionConfigApplyConfiguration) WithInstallationID(value string) *GitHubConnectionConfigApplyConfiguration { + b.InstallationID = &value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/gitlabconnectionconfig.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/gitlabconnectionconfig.go new file mode 100644 index 00000000000..0238ae3c226 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/gitlabconnectionconfig.go @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +// GitlabConnectionConfigApplyConfiguration represents a declarative configuration of the GitlabConnectionConfig type for use +// with apply. +type GitlabConnectionConfigApplyConfiguration struct { + ClientID *string `json:"clientID,omitempty"` +} + +// GitlabConnectionConfigApplyConfiguration constructs a declarative configuration of the GitlabConnectionConfig type for use with +// apply. +func GitlabConnectionConfig() *GitlabConnectionConfigApplyConfiguration { + return &GitlabConnectionConfigApplyConfiguration{} +} + +// WithClientID sets the ClientID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ClientID field is set to the value of the last call. +func (b *GitlabConnectionConfigApplyConfiguration) WithClientID(value string) *GitlabConnectionConfigApplyConfiguration { + b.ClientID = &value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/utils.go b/apps/provisioning/pkg/generated/applyconfiguration/utils.go index 400f2b7c083..52c3faee58c 100644 --- a/apps/provisioning/pkg/generated/applyconfiguration/utils.go +++ b/apps/provisioning/pkg/generated/applyconfiguration/utils.go @@ -18,14 +18,28 @@ import ( func ForKind(kind schema.GroupVersionKind) interface{} { switch kind { // Group=provisioning.grafana.app, Version=v0alpha1 + case v0alpha1.SchemeGroupVersion.WithKind("BitbucketConnectionConfig"): + return &provisioningv0alpha1.BitbucketConnectionConfigApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("BitbucketRepositoryConfig"): return &provisioningv0alpha1.BitbucketRepositoryConfigApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("Connection"): + return &provisioningv0alpha1.ConnectionApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("ConnectionSecure"): + return &provisioningv0alpha1.ConnectionSecureApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("ConnectionSpec"): + return &provisioningv0alpha1.ConnectionSpecApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("ConnectionStatus"): + return &provisioningv0alpha1.ConnectionStatusApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("DeleteJobOptions"): return &provisioningv0alpha1.DeleteJobOptionsApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("ExportJobOptions"): return &provisioningv0alpha1.ExportJobOptionsApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("GitHubConnectionConfig"): + return &provisioningv0alpha1.GitHubConnectionConfigApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("GitHubRepositoryConfig"): return &provisioningv0alpha1.GitHubRepositoryConfigApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("GitlabConnectionConfig"): + return &provisioningv0alpha1.GitlabConnectionConfigApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("GitLabRepositoryConfig"): return &provisioningv0alpha1.GitLabRepositoryConfigApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("GitRepositoryConfig"): diff --git a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/connection.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/connection.go new file mode 100644 index 00000000000..fe585e5c0bb --- /dev/null +++ b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/connection.go @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by client-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + context "context" + + provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + applyconfigurationprovisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1" + scheme "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// ConnectionsGetter has a method to return a ConnectionInterface. +// A group's client should implement this interface. +type ConnectionsGetter interface { + Connections(namespace string) ConnectionInterface +} + +// ConnectionInterface has methods to work with Connection resources. +type ConnectionInterface interface { + Create(ctx context.Context, connection *provisioningv0alpha1.Connection, opts v1.CreateOptions) (*provisioningv0alpha1.Connection, error) + Update(ctx context.Context, connection *provisioningv0alpha1.Connection, opts v1.UpdateOptions) (*provisioningv0alpha1.Connection, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, connection *provisioningv0alpha1.Connection, opts v1.UpdateOptions) (*provisioningv0alpha1.Connection, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*provisioningv0alpha1.Connection, error) + List(ctx context.Context, opts v1.ListOptions) (*provisioningv0alpha1.ConnectionList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *provisioningv0alpha1.Connection, err error) + Apply(ctx context.Context, connection *applyconfigurationprovisioningv0alpha1.ConnectionApplyConfiguration, opts v1.ApplyOptions) (result *provisioningv0alpha1.Connection, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, connection *applyconfigurationprovisioningv0alpha1.ConnectionApplyConfiguration, opts v1.ApplyOptions) (result *provisioningv0alpha1.Connection, err error) + ConnectionExpansion +} + +// connections implements ConnectionInterface +type connections struct { + *gentype.ClientWithListAndApply[*provisioningv0alpha1.Connection, *provisioningv0alpha1.ConnectionList, *applyconfigurationprovisioningv0alpha1.ConnectionApplyConfiguration] +} + +// newConnections returns a Connections +func newConnections(c *ProvisioningV0alpha1Client, namespace string) *connections { + return &connections{ + gentype.NewClientWithListAndApply[*provisioningv0alpha1.Connection, *provisioningv0alpha1.ConnectionList, *applyconfigurationprovisioningv0alpha1.ConnectionApplyConfiguration]( + "connections", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *provisioningv0alpha1.Connection { return &provisioningv0alpha1.Connection{} }, + func() *provisioningv0alpha1.ConnectionList { return &provisioningv0alpha1.ConnectionList{} }, + ), + } +} diff --git a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_connection.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_connection.go new file mode 100644 index 00000000000..2059d044172 --- /dev/null +++ b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_connection.go @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1" + typedprovisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1" + gentype "k8s.io/client-go/gentype" +) + +// fakeConnections implements ConnectionInterface +type fakeConnections struct { + *gentype.FakeClientWithListAndApply[*v0alpha1.Connection, *v0alpha1.ConnectionList, *provisioningv0alpha1.ConnectionApplyConfiguration] + Fake *FakeProvisioningV0alpha1 +} + +func newFakeConnections(fake *FakeProvisioningV0alpha1, namespace string) typedprovisioningv0alpha1.ConnectionInterface { + return &fakeConnections{ + gentype.NewFakeClientWithListAndApply[*v0alpha1.Connection, *v0alpha1.ConnectionList, *provisioningv0alpha1.ConnectionApplyConfiguration]( + fake.Fake, + namespace, + v0alpha1.SchemeGroupVersion.WithResource("connections"), + v0alpha1.SchemeGroupVersion.WithKind("Connection"), + func() *v0alpha1.Connection { return &v0alpha1.Connection{} }, + func() *v0alpha1.ConnectionList { return &v0alpha1.ConnectionList{} }, + func(dst, src *v0alpha1.ConnectionList) { dst.ListMeta = src.ListMeta }, + func(list *v0alpha1.ConnectionList) []*v0alpha1.Connection { return gentype.ToPointerSlice(list.Items) }, + func(list *v0alpha1.ConnectionList, items []*v0alpha1.Connection) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_provisioning_client.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_provisioning_client.go index d6fe94156be..2f1422cadc1 100644 --- a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_provisioning_client.go +++ b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/fake/fake_provisioning_client.go @@ -14,6 +14,10 @@ type FakeProvisioningV0alpha1 struct { *testing.Fake } +func (c *FakeProvisioningV0alpha1) Connections(namespace string) v0alpha1.ConnectionInterface { + return newFakeConnections(c, namespace) +} + func (c *FakeProvisioningV0alpha1) HistoricJobs(namespace string) v0alpha1.HistoricJobInterface { return newFakeHistoricJobs(c, namespace) } diff --git a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/generated_expansion.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/generated_expansion.go index 21b7a18b414..5220accc813 100644 --- a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/generated_expansion.go +++ b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/generated_expansion.go @@ -4,6 +4,8 @@ package v0alpha1 +type ConnectionExpansion interface{} + type HistoricJobExpansion interface{} type JobExpansion interface{} diff --git a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/provisioning_client.go b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/provisioning_client.go index 401bc533cfe..03a1e2e0ceb 100644 --- a/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/provisioning_client.go +++ b/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1/provisioning_client.go @@ -14,6 +14,7 @@ import ( type ProvisioningV0alpha1Interface interface { RESTClient() rest.Interface + ConnectionsGetter HistoricJobsGetter JobsGetter RepositoriesGetter @@ -24,6 +25,10 @@ type ProvisioningV0alpha1Client struct { restClient rest.Interface } +func (c *ProvisioningV0alpha1Client) Connections(namespace string) ConnectionInterface { + return newConnections(c, namespace) +} + func (c *ProvisioningV0alpha1Client) HistoricJobs(namespace string) HistoricJobInterface { return newHistoricJobs(c, namespace) } diff --git a/apps/provisioning/pkg/generated/informers/externalversions/generic.go b/apps/provisioning/pkg/generated/informers/externalversions/generic.go index 4a62aab044a..8429b75d959 100644 --- a/apps/provisioning/pkg/generated/informers/externalversions/generic.go +++ b/apps/provisioning/pkg/generated/informers/externalversions/generic.go @@ -39,6 +39,8 @@ func (f *genericInformer) Lister() cache.GenericLister { func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { // Group=provisioning.grafana.app, Version=v0alpha1 + case v0alpha1.SchemeGroupVersion.WithResource("connections"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Provisioning().V0alpha1().Connections().Informer()}, nil case v0alpha1.SchemeGroupVersion.WithResource("historicjobs"): return &genericInformer{resource: resource.GroupResource(), informer: f.Provisioning().V0alpha1().HistoricJobs().Informer()}, nil case v0alpha1.SchemeGroupVersion.WithResource("jobs"): diff --git a/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/connection.go b/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/connection.go new file mode 100644 index 00000000000..d8fa071219f --- /dev/null +++ b/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/connection.go @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by informer-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + context "context" + time "time" + + apisprovisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + versioned "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned" + internalinterfaces "github.com/grafana/grafana/apps/provisioning/pkg/generated/informers/externalversions/internalinterfaces" + provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// ConnectionInformer provides access to a shared informer and lister for +// Connections. +type ConnectionInformer interface { + Informer() cache.SharedIndexInformer + Lister() provisioningv0alpha1.ConnectionLister +} + +type connectionInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewConnectionInformer constructs a new informer for Connection type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewConnectionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredConnectionInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredConnectionInformer constructs a new informer for Connection type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredConnectionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProvisioningV0alpha1().Connections(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProvisioningV0alpha1().Connections(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProvisioningV0alpha1().Connections(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ProvisioningV0alpha1().Connections(namespace).Watch(ctx, options) + }, + }, + &apisprovisioningv0alpha1.Connection{}, + resyncPeriod, + indexers, + ) +} + +func (f *connectionInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredConnectionInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *connectionInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apisprovisioningv0alpha1.Connection{}, f.defaultInformer) +} + +func (f *connectionInformer) Lister() provisioningv0alpha1.ConnectionLister { + return provisioningv0alpha1.NewConnectionLister(f.Informer().GetIndexer()) +} diff --git a/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/interface.go b/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/interface.go index cbe2fcefaf3..1a1908ab5f3 100644 --- a/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/interface.go +++ b/apps/provisioning/pkg/generated/informers/externalversions/provisioning/v0alpha1/interface.go @@ -10,6 +10,8 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { + // Connections returns a ConnectionInformer. + Connections() ConnectionInformer // HistoricJobs returns a HistoricJobInformer. HistoricJobs() HistoricJobInformer // Jobs returns a JobInformer. @@ -29,6 +31,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// Connections returns a ConnectionInformer. +func (v *version) Connections() ConnectionInformer { + return &connectionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // HistoricJobs returns a HistoricJobInformer. func (v *version) HistoricJobs() HistoricJobInformer { return &historicJobInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/connection.go b/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/connection.go new file mode 100644 index 00000000000..a12902c2bea --- /dev/null +++ b/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/connection.go @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by lister-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + provisioningv0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// ConnectionLister helps list Connections. +// All objects returned here must be treated as read-only. +type ConnectionLister interface { + // List lists all Connections in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*provisioningv0alpha1.Connection, err error) + // Connections returns an object that can list and get Connections. + Connections(namespace string) ConnectionNamespaceLister + ConnectionListerExpansion +} + +// connectionLister implements the ConnectionLister interface. +type connectionLister struct { + listers.ResourceIndexer[*provisioningv0alpha1.Connection] +} + +// NewConnectionLister returns a new ConnectionLister. +func NewConnectionLister(indexer cache.Indexer) ConnectionLister { + return &connectionLister{listers.New[*provisioningv0alpha1.Connection](indexer, provisioningv0alpha1.Resource("connection"))} +} + +// Connections returns an object that can list and get Connections. +func (s *connectionLister) Connections(namespace string) ConnectionNamespaceLister { + return connectionNamespaceLister{listers.NewNamespaced[*provisioningv0alpha1.Connection](s.ResourceIndexer, namespace)} +} + +// ConnectionNamespaceLister helps list and get Connections. +// All objects returned here must be treated as read-only. +type ConnectionNamespaceLister interface { + // List lists all Connections in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*provisioningv0alpha1.Connection, err error) + // Get retrieves the Connection from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*provisioningv0alpha1.Connection, error) + ConnectionNamespaceListerExpansion +} + +// connectionNamespaceLister implements the ConnectionNamespaceLister +// interface. +type connectionNamespaceLister struct { + listers.ResourceIndexer[*provisioningv0alpha1.Connection] +} diff --git a/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/expansion_generated.go b/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/expansion_generated.go index 7f0649542b3..4d840b6e2db 100644 --- a/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/expansion_generated.go +++ b/apps/provisioning/pkg/generated/listers/provisioning/v0alpha1/expansion_generated.go @@ -4,6 +4,14 @@ package v0alpha1 +// ConnectionListerExpansion allows custom methods to be added to +// ConnectionLister. +type ConnectionListerExpansion interface{} + +// ConnectionNamespaceListerExpansion allows custom methods to be added to +// ConnectionNamespaceLister. +type ConnectionNamespaceListerExpansion interface{} + // HistoricJobListerExpansion allows custom methods to be added to // HistoricJobLister. type HistoricJobListerExpansion interface{} diff --git a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts index 3c6e905683c..cb2aa3d08e2 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts @@ -1,5 +1,5 @@ import { api } from './baseAPI'; -export const addTagTypes = ['API Discovery', 'Job', 'Repository', 'Provisioning'] as const; +export const addTagTypes = ['API Discovery', 'Connection', 'Job', 'Repository', 'Provisioning'] as const; const injectedRtkApi = api .enhanceEndpoints({ addTagTypes, @@ -10,6 +10,156 @@ const injectedRtkApi = api query: () => ({ url: `/` }), providesTags: ['API Discovery'], }), + listConnection: build.query({ + query: (queryArg) => ({ + url: `/connections`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['Connection'], + }), + createConnection: build.mutation({ + query: (queryArg) => ({ + url: `/connections`, + method: 'POST', + body: queryArg.connection, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Connection'], + }), + deletecollectionConnection: build.mutation< + DeletecollectionConnectionApiResponse, + DeletecollectionConnectionApiArg + >({ + query: (queryArg) => ({ + url: `/connections`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['Connection'], + }), + getConnection: build.query({ + query: (queryArg) => ({ + url: `/connections/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['Connection'], + }), + replaceConnection: build.mutation({ + query: (queryArg) => ({ + url: `/connections/${queryArg.name}`, + method: 'PUT', + body: queryArg.connection, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Connection'], + }), + deleteConnection: build.mutation({ + query: (queryArg) => ({ + url: `/connections/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['Connection'], + }), + updateConnection: build.mutation({ + query: (queryArg) => ({ + url: `/connections/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Connection'], + }), + getConnectionStatus: build.query({ + query: (queryArg) => ({ + url: `/connections/${queryArg.name}/status`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['Connection'], + }), + replaceConnectionStatus: build.mutation({ + query: (queryArg) => ({ + url: `/connections/${queryArg.name}/status`, + method: 'PUT', + body: queryArg.connection, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Connection'], + }), + updateConnectionStatus: build.mutation({ + query: (queryArg) => ({ + url: `/connections/${queryArg.name}/status`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Connection'], + }), listJob: build.query({ query: (queryArg) => ({ url: `/jobs`, @@ -411,6 +561,208 @@ const injectedRtkApi = api export { injectedRtkApi as generatedAPI }; export type GetApiResourcesApiResponse = /** status 200 OK */ ApiResourceList; export type GetApiResourcesApiArg = void; +export type ListConnectionApiResponse = /** status 200 OK */ ConnectionList; +export type ListConnectionApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** allowWatchBookmarks requests watch events with type "BOOKMARK". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. */ + allowWatchBookmarks?: boolean; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateConnectionApiResponse = /** status 200 OK */ + | Connection + | /** status 201 Created */ Connection + | /** status 202 Accepted */ Connection; +export type CreateConnectionApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + connection: Connection; +}; +export type DeletecollectionConnectionApiResponse = /** status 200 OK */ Status; +export type DeletecollectionConnectionApiArg = { + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetConnectionApiResponse = /** status 200 OK */ Connection; +export type GetConnectionApiArg = { + /** name of the Connection */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceConnectionApiResponse = /** status 200 OK */ Connection | /** status 201 Created */ Connection; +export type ReplaceConnectionApiArg = { + /** name of the Connection */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + connection: Connection; +}; +export type DeleteConnectionApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteConnectionApiArg = { + /** name of the Connection */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateConnectionApiResponse = /** status 200 OK */ Connection | /** status 201 Created */ Connection; +export type UpdateConnectionApiArg = { + /** name of the Connection */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch). */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + /** Force is going to "force" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests. */ + force?: boolean; + patch: Patch; +}; +export type GetConnectionStatusApiResponse = /** status 200 OK */ Connection; +export type GetConnectionStatusApiArg = { + /** name of the Connection */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceConnectionStatusApiResponse = /** status 200 OK */ Connection | /** status 201 Created */ Connection; +export type ReplaceConnectionStatusApiArg = { + /** name of the Connection */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + connection: Connection; +}; +export type UpdateConnectionStatusApiResponse = /** status 200 OK */ Connection | /** status 201 Created */ Connection; +export type UpdateConnectionStatusApiArg = { + /** name of the Connection */ + name: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch). */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + /** Force is going to "force" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests. */ + force?: boolean; + patch: Patch; +}; export type ListJobApiResponse = /** status 200 OK */ JobList; export type ListJobApiArg = { /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ @@ -1053,6 +1405,169 @@ export type ObjectMeta = { Populated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ uid?: string; }; +export type InlineSecureValue = + | { + /** Create a secure value -- this is only used for POST/PUT */ + create?: string; + /** Name in the secret service (reference) */ + name: string; + /** Remove this value from the secure value map Values owned by this resource will be deleted if necessary */ + remove?: boolean; + } + | { + /** Create a secure value -- this is only used for POST/PUT */ + create: string; + /** Name in the secret service (reference) */ + name?: string; + /** Remove this value from the secure value map Values owned by this resource will be deleted if necessary */ + remove?: boolean; + } + | { + /** Create a secure value -- this is only used for POST/PUT */ + create?: string; + /** Name in the secret service (reference) */ + name?: string; + /** Remove this value from the secure value map Values owned by this resource will be deleted if necessary */ + remove: boolean; + }; +export type ConnectionSecure = { + /** ClientSecret is the reference to the secret used for other providers authentication, and Github on-behalf-of authentication. This value is stored securely and cannot be read back */ + clientSecret?: InlineSecureValue; + /** PrivateKey is the reference to the private key used for GitHub App authentication. This value is stored securely and cannot be read back */ + privateKey?: InlineSecureValue; + /** Token is the reference of the token used to act as the Connection. This value is stored securely and cannot be read back */ + webhook?: InlineSecureValue; +}; +export type BitbucketConnectionConfig = { + /** App client ID */ + clientID: string; +}; +export type GitHubConnectionConfig = { + /** GitHub App ID */ + appID: string; + /** GitHub App installation ID */ + installationID: string; +}; +export type GitlabConnectionConfig = { + /** App client ID */ + clientID: string; +}; +export type ConnectionSpec = { + /** Bitbucket connection configuration Only applicable when provider is "bitbucket" */ + bitbucket?: BitbucketConnectionConfig; + /** GitHub connection configuration Only applicable when provider is "github" */ + github?: GitHubConnectionConfig; + /** Gitlab connection configuration Only applicable when provider is "gitlab" */ + gitlab?: GitlabConnectionConfig; + /** The connection provider type + + Possible enum values: + - `"bitbucket"` + - `"github"` + - `"gitlab"` */ + type: 'bitbucket' | 'github' | 'gitlab'; + /** The connection URL */ + url?: string; +}; +export type HealthStatus = { + /** When the health was checked last time */ + checked?: number; + /** The type of the error + + Possible enum values: + - `"health"` + - `"hook"` */ + error?: 'health' | 'hook'; + /** When not healthy, requests will not be executed */ + healthy: boolean; + /** Summary messages (can be shown to users) Will only be populated when not healthy */ + message?: string[]; +}; +export type ConnectionStatus = { + /** The connection health status */ + health: HealthStatus; + /** The generation of the spec last time reconciliation ran */ + observedGeneration: number; + /** Connection state + + Possible enum values: + - `"connected"` + - `"disconnected"` */ + state: 'connected' | 'disconnected'; +}; +export type Connection = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata?: ObjectMeta; + secure?: ConnectionSecure; + spec?: ConnectionSpec; + status?: ConnectionStatus; +}; +export type ListMeta = { + /** continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message. */ + continue?: string; + /** remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact. */ + remainingItemCount?: number; + /** String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency */ + resourceVersion?: string; + /** Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. */ + selfLink?: string; +}; +export type ConnectionList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: Connection[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata?: ListMeta; +}; +export type StatusCause = { + /** The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. + + Examples: + "name" - the field "name" on the current resource + "items[0].name" - the field "name" on the first array entry in "items" */ + field?: string; + /** A human-readable description of the cause of the error. This field may be presented as-is to a reader. */ + message?: string; + /** A machine-readable description of the cause of the error. If this value is empty there is no information available. */ + reason?: string; +}; +export type StatusDetails = { + /** The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes. */ + causes?: StatusCause[]; + /** The group attribute of the resource associated with the status StatusReason. */ + group?: string; + /** The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described). */ + name?: string; + /** If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action. */ + retryAfterSeconds?: number; + /** UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ + uid?: string; +}; +export type Status = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Suggested HTTP return code for this status, 0 if not set. */ + code?: number; + /** Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type. */ + details?: StatusDetails; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** A human-readable description of the status of this operation. */ + message?: string; + /** Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + metadata?: ListMeta; + /** A machine-readable description of why this operation is in the "Failure" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it. */ + reason?: string; + /** Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status */ + status?: string; +}; +export type Patch = object; export type ResourceRef = { /** Group is the group of the resource, such as "dashboard.grafana.app". */ group?: string; @@ -1190,16 +1705,6 @@ export type Job = { spec?: JobSpec; status?: JobStatus; }; -export type ListMeta = { - /** continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message. */ - continue?: string; - /** remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact. */ - remainingItemCount?: number; - /** String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency */ - resourceVersion?: string; - /** Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. */ - selfLink?: string; -}; export type JobList = { /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ apiVersion?: string; @@ -1208,76 +1713,6 @@ export type JobList = { kind?: string; metadata?: ListMeta; }; -export type StatusCause = { - /** The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. - - Examples: - "name" - the field "name" on the current resource - "items[0].name" - the field "name" on the first array entry in "items" */ - field?: string; - /** A human-readable description of the cause of the error. This field may be presented as-is to a reader. */ - message?: string; - /** A machine-readable description of the cause of the error. If this value is empty there is no information available. */ - reason?: string; -}; -export type StatusDetails = { - /** The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes. */ - causes?: StatusCause[]; - /** The group attribute of the resource associated with the status StatusReason. */ - group?: string; - /** The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ - kind?: string; - /** The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described). */ - name?: string; - /** If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action. */ - retryAfterSeconds?: number; - /** UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ - uid?: string; -}; -export type Status = { - /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ - apiVersion?: string; - /** Suggested HTTP return code for this status, 0 if not set. */ - code?: number; - /** Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type. */ - details?: StatusDetails; - /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ - kind?: string; - /** A human-readable description of the status of this operation. */ - message?: string; - /** Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ - metadata?: ListMeta; - /** A machine-readable description of why this operation is in the "Failure" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it. */ - reason?: string; - /** Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status */ - status?: string; -}; -export type Patch = object; -export type InlineSecureValue = - | { - /** Create a secure value -- this is only used for POST/PUT */ - create?: string; - /** Name in the secret service (reference) */ - name: string; - /** Remove this value from the secure value map Values owned by this resource will be deleted if necessary */ - remove?: boolean; - } - | { - /** Create a secure value -- this is only used for POST/PUT */ - create: string; - /** Name in the secret service (reference) */ - name?: string; - /** Remove this value from the secure value map Values owned by this resource will be deleted if necessary */ - remove?: boolean; - } - | { - /** Create a secure value -- this is only used for POST/PUT */ - create?: string; - /** Name in the secret service (reference) */ - name?: string; - /** Remove this value from the secure value map Values owned by this resource will be deleted if necessary */ - remove: boolean; - }; export type SecureValues = { /** Token used to connect the configured repository */ token?: InlineSecureValue; @@ -1374,20 +1809,6 @@ export type RepositorySpec = { /** UI driven Workflow that allow changes to the contends of the repository. The order is relevant for defining the precedence of the workflows. When empty, the repository does not support any edits (eg, readonly) */ workflows: ('branch' | 'write')[]; }; -export type HealthStatus = { - /** When the health was checked last time */ - checked?: number; - /** The type of the error - - Possible enum values: - - `"health"` - - `"hook"` */ - error?: 'health' | 'hook'; - /** When not healthy, requests will not be executed */ - healthy: boolean; - /** Summary messages (can be shown to users) Will only be populated when not healthy */ - message?: string[]; -}; export type ResourceCount = { count: number; group: string; @@ -1648,6 +2069,19 @@ export type ResourceStats = { export const { useGetApiResourcesQuery, useLazyGetApiResourcesQuery, + useListConnectionQuery, + useLazyListConnectionQuery, + useCreateConnectionMutation, + useDeletecollectionConnectionMutation, + useGetConnectionQuery, + useLazyGetConnectionQuery, + useReplaceConnectionMutation, + useDeleteConnectionMutation, + useUpdateConnectionMutation, + useGetConnectionStatusQuery, + useLazyGetConnectionStatusQuery, + useReplaceConnectionStatusMutation, + useUpdateConnectionStatusMutation, useListJobQuery, useLazyListJobQuery, useCreateJobMutation, diff --git a/pkg/registry/apis/provisioning/controller/repository_test.go b/pkg/registry/apis/provisioning/controller/repository_test.go index 8358ef9088d..9390d98ec34 100644 --- a/pkg/registry/apis/provisioning/controller/repository_test.go +++ b/pkg/registry/apis/provisioning/controller/repository_test.go @@ -39,6 +39,10 @@ func (m mockProvisioningV0alpha1Interface) Jobs(namespace string) client.JobInte panic("not needed for testing") } +func (m mockProvisioningV0alpha1Interface) Connections(namespace string) client.ConnectionInterface { + panic("not needed for testing") +} + func (m mockProvisioningV0alpha1Interface) Repositories(namespace string) client.RepositoryInterface { if m.repositoriesFunc != nil { return m.repositoriesFunc(namespace) diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index cbf50a027f4..b479dc3f8c8 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -31,6 +31,7 @@ import ( dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + connectionvalidation "github.com/grafana/grafana/apps/provisioning/pkg/connection" appcontroller "github.com/grafana/grafana/apps/provisioning/pkg/controller" clientset "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned" client "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned/typed/provisioning/v0alpha1" @@ -354,6 +355,8 @@ func (b *APIBuilder) authorizeResource(ctx context.Context, a authorizer.Attribu return b.authorizeSettings(id) case provisioning.JobResourceInfo.GetName(), provisioning.HistoricJobResourceInfo.GetName(): return b.authorizeJobs(id) + case provisioning.ConnectionResourceInfo.GetName(): + return b.authorizeConnectionSubresource(a, id) default: return b.authorizeDefault(id) } @@ -437,6 +440,28 @@ func (b *APIBuilder) authorizeJobs(id identity.Requester) (authorizer.Decision, return authorizer.DecisionDeny, "admin role is required", nil } +// authorizeRepositorySubresource handles authorization for connections subresources. +func (b *APIBuilder) authorizeConnectionSubresource(a authorizer.Attributes, id identity.Requester) (authorizer.Decision, string, error) { + switch a.GetSubresource() { + case "": + // Doing something with the connection itself. + if id.GetOrgRole().Includes(identity.RoleAdmin) { + return authorizer.DecisionAllow, "", nil + } + return authorizer.DecisionDeny, "admin role is required", nil + case "status": + if id.GetOrgRole().Includes(identity.RoleViewer) && a.GetVerb() == apiutils.VerbGet { + return authorizer.DecisionAllow, "", nil + } + return authorizer.DecisionDeny, "users cannot update the status of a connection", nil + default: + if id.GetIsGrafanaAdmin() { + return authorizer.DecisionAllow, "", nil + } + return authorizer.DecisionDeny, "unmapped subresource defaults to no access", nil + } +} + // authorizeDefault handles authorization for unmapped resources. func (b *APIBuilder) authorizeDefault(id identity.Requester) (authorizer.Decision, string, error) { // We haven't bothered with this kind yet. @@ -520,10 +545,19 @@ func (b *APIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupI storage[provisioning.HistoricJobResourceInfo.StoragePath()] = historicJobStore } + connectionsStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, provisioning.ConnectionResourceInfo, opts.OptsGetter) + if err != nil { + return fmt.Errorf("failed to create connection storage: %w", err) + } + connectionStatusStorage := grafanaregistry.NewRegistryStatusStore(opts.Scheme, connectionsStore) + storage[provisioning.JobResourceInfo.StoragePath()] = jobStore storage[provisioning.RepositoryResourceInfo.StoragePath()] = repositoryStorage storage[provisioning.RepositoryResourceInfo.StoragePath("status")] = repositoryStatusStorage + storage[provisioning.ConnectionResourceInfo.StoragePath()] = connectionsStore + storage[provisioning.ConnectionResourceInfo.StoragePath("status")] = connectionStatusStorage + // TODO: Add some logic so that the connectors can registered themselves and we don't have logic all over the place storage[provisioning.RepositoryResourceInfo.StoragePath("test")] = NewTestConnector(b, repository.NewRepositoryTesterWithExistingChecker(repository.NewSimpleRepositoryTester(b.validator), b.VerifyAgainstExistingRepositories)) storage[provisioning.RepositoryResourceInfo.StoragePath("files")] = NewFilesConnector(b, b.parsers, b.clients, b.access) @@ -566,6 +600,11 @@ func (b *APIBuilder) Mutate(ctx context.Context, a admission.Attributes, o admis if ok { return nil } + // TODO: complete this as part of https://github.com/grafana/git-ui-sync-project/issues/700 + c, ok := obj.(*provisioning.Connection) + if ok { + return connectionvalidation.MutateConnection(c) + } r, ok := obj.(*provisioning.Repository) if !ok { @@ -615,6 +654,11 @@ func (b *APIBuilder) Validate(ctx context.Context, a admission.Attributes, o adm return nil } + connection, ok := obj.(*provisioning.Connection) + if ok { + return connectionvalidation.ValidateConnection(connection) + } + // Validate Jobs job, ok := obj.(*provisioning.Job) if ok { diff --git a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json index ab232f8674c..0eeb7737af2 100644 --- a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json @@ -36,6 +36,1126 @@ } } }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/connections": { + "get": { + "tags": [ + "Connection" + ], + "description": "list or watch objects of kind Connection", + "operationId": "listConnection", + "parameters": [ + { + "name": "allowWatchBookmarks", + "in": "query", + "description": "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "continue", + "in": "query", + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldSelector", + "in": "query", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "labelSelector", + "in": "query", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "limit", + "in": "query", + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "name": "resourceVersion", + "in": "query", + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "resourceVersionMatch", + "in": "query", + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "sendInitialEvents", + "in": "query", + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "timeoutSeconds", + "in": "query", + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "name": "watch", + "in": "query", + "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ConnectionList" + } + }, + "application/json;stream=watch": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ConnectionList" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ConnectionList" + } + }, + "application/vnd.kubernetes.protobuf;stream=watch": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ConnectionList" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ConnectionList" + } + } + } + } + }, + "x-kubernetes-action": "list", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "Connection" + } + }, + "post": { + "tags": [ + "Connection" + ], + "description": "create a Connection", + "operationId": "createConnection", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + } + }, + "202": { + "description": "Accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + } + } + }, + "x-kubernetes-action": "post", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "Connection" + } + }, + "delete": { + "tags": [ + "Connection" + ], + "description": "delete collection of Connection", + "operationId": "deletecollectionConnection", + "parameters": [ + { + "name": "continue", + "in": "query", + "description": "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldSelector", + "in": "query", + "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "gracePeriodSeconds", + "in": "query", + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "in": "query", + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "labelSelector", + "in": "query", + "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "limit", + "in": "query", + "description": "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "name": "orphanDependents", + "in": "query", + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "propagationPolicy", + "in": "query", + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "resourceVersion", + "in": "query", + "description": "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "resourceVersionMatch", + "in": "query", + "description": "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "sendInitialEvents", + "in": "query", + "description": "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "timeoutSeconds", + "in": "query", + "description": "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + "schema": { + "type": "integer", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + } + } + }, + "x-kubernetes-action": "deletecollection", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "Connection" + } + }, + "parameters": [ + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "pretty", + "in": "query", + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/connections/{name}": { + "get": { + "tags": [ + "Connection" + ], + "description": "read the specified Connection", + "operationId": "getConnection", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + } + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "Connection" + } + }, + "put": { + "tags": [ + "Connection" + ], + "description": "replace the specified Connection", + "operationId": "replaceConnection", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + } + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "Connection" + } + }, + "delete": { + "tags": [ + "Connection" + ], + "description": "delete a Connection", + "operationId": "deleteConnection", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "gracePeriodSeconds", + "in": "query", + "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "schema": { + "type": "integer", + "uniqueItems": true + } + }, + { + "name": "ignoreStoreReadErrorWithClusterBreakingPotential", + "in": "query", + "description": "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "orphanDependents", + "in": "query", + "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + }, + { + "name": "propagationPolicy", + "in": "query", + "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + } + }, + "202": { + "description": "Accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Status" + } + } + } + } + }, + "x-kubernetes-action": "delete", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "Connection" + } + }, + "patch": { + "tags": [ + "Connection" + ], + "description": "partially update the specified Connection", + "operationId": "updateConnection", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "force", + "in": "query", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + } + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "Connection" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the Connection", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "pretty", + "in": "query", + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, + "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/connections/{name}/status": { + "get": { + "tags": [ + "Connection" + ], + "description": "read status of the specified Connection", + "operationId": "getConnectionStatus", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + } + } + }, + "x-kubernetes-action": "get", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "Connection" + } + }, + "put": { + "tags": [ + "Connection" + ], + "description": "replace status of the specified Connection", + "operationId": "replaceConnectionStatus", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + } + } + }, + "x-kubernetes-action": "put", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "Connection" + } + }, + "patch": { + "tags": [ + "Connection" + ], + "description": "partially update status of the specified Connection", + "operationId": "updateConnectionStatus", + "parameters": [ + { + "name": "dryRun", + "in": "query", + "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldManager", + "in": "query", + "description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "fieldValidation", + "in": "query", + "description": "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "force", + "in": "query", + "description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + "schema": { + "type": "boolean", + "uniqueItems": true + } + } + ], + "requestBody": { + "content": { + "application/apply-patch+yaml": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + }, + "application/strategic-merge-patch+json": { + "schema": { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + } + }, + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/vnd.kubernetes.protobuf": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + } + } + } + }, + "x-kubernetes-action": "patch", + "x-kubernetes-group-version-kind": { + "group": "provisioning.grafana.app", + "version": "v0alpha1", + "kind": "Connection" + } + }, + "parameters": [ + { + "name": "name", + "in": "path", + "description": "name of the Connection", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "namespace", + "in": "path", + "description": "object name and auth scope, such as for teams and projects", + "required": true, + "schema": { + "type": "string", + "uniqueItems": true + } + }, + { + "name": "pretty", + "in": "query", + "description": "If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget).", + "schema": { + "type": "string", + "uniqueItems": true + } + } + ] + }, "/apis/provisioning.grafana.app/v0alpha1/namespaces/{namespace}/jobs": { "get": { "tags": [ @@ -3198,6 +4318,19 @@ } } }, + "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.BitbucketConnectionConfig": { + "type": "object", + "required": [ + "clientID" + ], + "properties": { + "clientID": { + "description": "App client ID", + "type": "string", + "default": "" + } + } + }, "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.BitbucketRepositoryConfig": { "type": "object", "required": [ @@ -3223,6 +4356,215 @@ } } }, + "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection": { + "description": "When this code is changed, make sure to update the code generation. As of writing, this can be done via the hack dir in the root of the repo: ./hack/update-codegen.sh provisioning If you've opened the generated files in this dir at some point in VSCode, you may also have to re-open them to clear errors.", + "type": "object", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" + } + ] + }, + "secure": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ConnectionSecure" + } + ] + }, + "spec": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ConnectionSpec" + } + ] + }, + "status": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ConnectionStatus" + } + ] + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "provisioning.grafana.app", + "kind": "Connection", + "version": "v0alpha1" + } + ] + }, + "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ConnectionList": { + "type": "object", + "required": [ + "items" + ], + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "type": "array", + "items": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.Connection" + } + ] + }, + "x-kubernetes-list-type": "atomic" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" + } + ] + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "provisioning.grafana.app", + "kind": "ConnectionList", + "version": "v0alpha1" + } + ] + }, + "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ConnectionSecure": { + "type": "object", + "properties": { + "clientSecret": { + "description": "ClientSecret is the reference to the secret used for other providers authentication, and Github on-behalf-of authentication. This value is stored securely and cannot be read back", + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.InlineSecureValue" + } + ] + }, + "privateKey": { + "description": "PrivateKey is the reference to the private key used for GitHub App authentication. This value is stored securely and cannot be read back", + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.InlineSecureValue" + } + ] + }, + "webhook": { + "description": "Token is the reference of the token used to act as the Connection. This value is stored securely and cannot be read back", + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.pkg.apimachinery.apis.common.v0alpha1.InlineSecureValue" + } + ] + } + } + }, + "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ConnectionSpec": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "bitbucket": { + "description": "Bitbucket connection configuration Only applicable when provider is \"bitbucket\"", + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.BitbucketConnectionConfig" + } + ] + }, + "github": { + "description": "GitHub connection configuration Only applicable when provider is \"github\"", + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.GitHubConnectionConfig" + } + ] + }, + "gitlab": { + "description": "Gitlab connection configuration Only applicable when provider is \"gitlab\"", + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.GitlabConnectionConfig" + } + ] + }, + "type": { + "description": "The connection provider type\n\nPossible enum values:\n - `\"bitbucket\"`\n - `\"github\"`\n - `\"gitlab\"`", + "type": "string", + "default": "", + "enum": [ + "bitbucket", + "github", + "gitlab" + ] + }, + "url": { + "description": "The connection URL", + "type": "string" + } + } + }, + "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ConnectionStatus": { + "description": "The status of a Connection. This is expected never to be created by a kubectl call or similar, and is expected to rarely (if ever) be edited manually.", + "type": "object", + "required": [ + "observedGeneration", + "state", + "health" + ], + "properties": { + "health": { + "description": "The connection health status", + "default": {}, + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.HealthStatus" + } + ] + }, + "observedGeneration": { + "description": "The generation of the spec last time reconciliation ran", + "type": "integer", + "format": "int64", + "default": 0 + }, + "state": { + "description": "Connection state\n\nPossible enum values:\n - `\"connected\"`\n - `\"disconnected\"`", + "type": "string", + "default": "", + "enum": [ + "connected", + "disconnected" + ] + } + } + }, "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.DeleteJobOptions": { "type": "object", "properties": { @@ -3344,6 +4686,25 @@ } } }, + "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.GitHubConnectionConfig": { + "type": "object", + "required": [ + "appID", + "installationID" + ], + "properties": { + "appID": { + "description": "GitHub App ID", + "type": "string", + "default": "" + }, + "installationID": { + "description": "GitHub App installation ID", + "type": "string", + "default": "" + } + } + }, "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.GitHubRepositoryConfig": { "type": "object", "required": [ @@ -3415,6 +4776,19 @@ } } }, + "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.GitlabConnectionConfig": { + "type": "object", + "required": [ + "clientID" + ], + "properties": { + "clientID": { + "description": "App client ID", + "type": "string", + "default": "" + } + } + }, "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.HealthStatus": { "type": "object", "required": [ diff --git a/pkg/tests/apis/provisioning/connection_test.go b/pkg/tests/apis/provisioning/connection_test.go new file mode 100644 index 00000000000..ea28ac88359 --- /dev/null +++ b/pkg/tests/apis/provisioning/connection_test.go @@ -0,0 +1,413 @@ +package provisioning + +import ( + "context" + "errors" + "testing" + + "github.com/grafana/grafana/pkg/util/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestIntegrationProvisioning_ConnectionCRUDL(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + helper := runGrafana(t) + createOptions := metav1.CreateOptions{FieldValidation: "Strict"} + ctx := context.Background() + + t.Run("should perform CRUDL requests on connection", func(t *testing.T) { + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "github", + "github": map[string]any{ + "appID": "123456", + "installationID": "454545", + }, + }, + "secure": map[string]any{ + "privateKey": map[string]any{ + "create": "someSecret", + }, + }, + }} + // CREATE + _, err := helper.Connections.Resource.Create(ctx, connection, createOptions) + require.NoError(t, err, "failed to create resource") + + // READ + output, err := helper.Connections.Resource.Get(ctx, "connection", metav1.GetOptions{}) + require.NoError(t, err, "failed to read back resource") + assert.Equal(t, "connection", output.GetName(), "name should be equal") + assert.Equal(t, "default", output.GetNamespace(), "namespace should be equal") + spec := output.Object["spec"].(map[string]any) + assert.Equal(t, "github", spec["type"], "type should be equal") + assert.Equal(t, "https://github.com/settings/installations/454545", spec["url"], "url should be equal") + require.Contains(t, spec, "github") + githubInfo := spec["github"].(map[string]any) + assert.Equal(t, "123456", githubInfo["appID"], "appID should be equal") + assert.Equal(t, "454545", githubInfo["installationID"], "installationID should be equal") + require.Contains(t, output.Object, "secure", "object should contain secure") + assert.Contains(t, output.Object["secure"], "privateKey", "secure should contain PrivateKey") + + // LIST + list, err := helper.Connections.Resource.List(ctx, metav1.ListOptions{}) + require.NoError(t, err, "failed to list resource") + assert.Equal(t, 1, len(list.Items), "should have one connection") + assert.Equal(t, "connection", list.Items[0].GetName(), "name should be equal") + + // UPDATE + updatedConnection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "github", + "github": map[string]any{ + "appID": "456789", + "installationID": "454545", + }, + }, + "secure": map[string]any{ + "privateKey": map[string]any{ + "create": "someSecret", + }, + }, + }} + res, err := helper.Connections.Resource.Update(ctx, updatedConnection, metav1.UpdateOptions{}) + require.NoError(t, err, "failed to update resource") + spec = res.Object["spec"].(map[string]any) + require.Contains(t, spec, "github") + githubInfo = spec["github"].(map[string]any) + assert.Equal(t, "456789", githubInfo["appID"], "appID should be updated") + + // DELETE + require.NoError(t, helper.Connections.Resource.Delete(ctx, "connection", metav1.DeleteOptions{}), "failed to delete resource") + list, err = helper.Connections.Resource.List(ctx, metav1.ListOptions{}) + require.NoError(t, err, "failed to list resources") + assert.Equal(t, 0, len(list.Items), "should have no connections") + }) + + t.Run("viewer can't create or get connection", func(t *testing.T) { + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "github", + "github": map[string]any{ + "appID": "123456", + "installationID": "454545", + }, + }, + "secure": map[string]any{ + "privateKey": map[string]any{ + "create": "someSecret", + }, + }, + }} + + result := helper.ViewerREST.Post(). + Namespace("default"). + Resource("connections"). + Body(connection). + Do(t.Context()) + + require.NotNil(t, result.Error()) + err := &k8serrors.StatusError{} + require.True(t, errors.As(result.Error(), &err)) + assert.Equal(t, metav1.StatusReasonForbidden, err.Status().Reason) + assert.Contains(t, err.Status().Message, "User \"viewer\" cannot create resource \"connections\"") + assert.Contains(t, err.Status().Message, "admin role is required") + + result = helper.ViewerREST.Get(). + Namespace("default"). + Resource("connections"). + Name("connection"). + Do(t.Context()) + require.NotNil(t, result.Error()) + err = &k8serrors.StatusError{} + require.True(t, errors.As(result.Error(), &err)) + assert.Equal(t, metav1.StatusReasonForbidden, err.Status().Reason) + assert.Contains(t, err.Status().Message, "User \"viewer\" cannot get resource \"connections\"") + assert.Contains(t, err.Status().Message, "admin role is required") + }) +} + +func TestIntegrationProvisioning_ConnectionValidation(t *testing.T) { + helper := runGrafana(t) + createOptions := metav1.CreateOptions{FieldValidation: "Strict"} + ctx := context.Background() + + t.Run("should fail when type is empty", func(t *testing.T) { + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "", + }, + "secure": map[string]any{ + "privateKey": map[string]any{ + "create": "someSecret", + }, + }, + }} + _, err := helper.Connections.Resource.Create(ctx, connection, createOptions) + require.Error(t, err, "failed to create resource") + assert.Contains(t, err.Error(), "type must be specified") + }) + + t.Run("should fail when type is invalid", func(t *testing.T) { + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "some-invalid-type", + }, + "secure": map[string]any{ + "privateKey": map[string]any{ + "create": "someSecret", + }, + }, + }} + _, err := helper.Connections.Resource.Create(ctx, connection, createOptions) + require.Error(t, err, "failed to create resource") + assert.Contains(t, err.Error(), "spec.type: Unsupported value: \"some-invalid-type\"") + }) + + t.Run("should fail when type is github but 'github' field is not there", func(t *testing.T) { + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "github", + }, + "secure": map[string]any{ + "privateKey": map[string]any{ + "create": "someSecret", + }, + }, + }} + _, err := helper.Connections.Resource.Create(ctx, connection, createOptions) + require.Error(t, err, "failed to create resource") + assert.Contains(t, err.Error(), "github info must be specified for GitHub connection") + }) + + t.Run("should fail when type is github but private key is not there", func(t *testing.T) { + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "github", + "github": map[string]any{ + "appID": "123456", + "installationID": "454545", + }, + }, + }} + _, err := helper.Connections.Resource.Create(ctx, connection, createOptions) + require.Error(t, err, "failed to create resource") + assert.Contains(t, err.Error(), "privateKey must be specified for GitHub connection") + }) + + t.Run("should fail when type is github but a client Secret is specified", func(t *testing.T) { + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "github", + "github": map[string]any{ + "appID": "123456", + "installationID": "454545", + }, + }, + "secure": map[string]any{ + "privateKey": map[string]any{ + "create": "someSecret", + }, + "clientSecret": map[string]any{ + "create": "someSecret", + }, + }, + }} + _, err := helper.Connections.Resource.Create(ctx, connection, createOptions) + require.Error(t, err, "failed to create resource") + assert.Contains(t, err.Error(), "clientSecret is forbidden in GitHub connection") + }) + + t.Run("should fail when type is bitbucket but 'bitbucket' field is not there", func(t *testing.T) { + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "bitbucket", + }, + "secure": map[string]any{ + "clientSecret": map[string]any{ + "create": "someSecret", + }, + }, + }} + _, err := helper.Connections.Resource.Create(ctx, connection, createOptions) + require.Error(t, err, "failed to create resource") + assert.Contains(t, err.Error(), "bitbucket info must be specified in Bitbucket connection") + }) + + t.Run("should fail when type is bitbucket but client secret is not there", func(t *testing.T) { + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "bitbucket", + "bitbucket": map[string]any{ + "clientID": "123456", + }, + }, + }} + _, err := helper.Connections.Resource.Create(ctx, connection, createOptions) + require.Error(t, err, "failed to create resource") + assert.Contains(t, err.Error(), "clientSecret must be specified for Bitbucket connection") + }) + + t.Run("should fail when type is bitbucket but a private key is specified", func(t *testing.T) { + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "bitbucket", + "bitbucket": map[string]any{ + "clientID": "123456", + }, + }, + "secure": map[string]any{ + "privateKey": map[string]any{ + "create": "someSecret", + }, + "clientSecret": map[string]any{ + "create": "someSecret", + }, + }, + }} + _, err := helper.Connections.Resource.Create(ctx, connection, createOptions) + require.Error(t, err, "failed to create resource") + assert.Contains(t, err.Error(), "privateKey is forbidden in Bitbucket connection") + }) + + t.Run("should fail when type is gitlab but 'gitlab' field is not there", func(t *testing.T) { + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "gitlab", + }, + "secure": map[string]any{ + "clientSecret": map[string]any{ + "create": "someSecret", + }, + }, + }} + _, err := helper.Connections.Resource.Create(ctx, connection, createOptions) + require.Error(t, err, "failed to create resource") + assert.Contains(t, err.Error(), "gitlab info must be specified in Gitlab connection") + }) + + t.Run("should fail when type is gitlab but client secret is not there", func(t *testing.T) { + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "gitlab", + "gitlab": map[string]any{ + "clientID": "123456", + }, + }, + }} + _, err := helper.Connections.Resource.Create(ctx, connection, createOptions) + require.Error(t, err, "failed to create resource") + assert.Contains(t, err.Error(), "clientSecret must be specified for Gitlab connection") + }) + + t.Run("should fail when type is gitlab but a private key is specified", func(t *testing.T) { + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "gitlab", + "gitlab": map[string]any{ + "clientID": "123456", + }, + }, + "secure": map[string]any{ + "privateKey": map[string]any{ + "create": "someSecret", + }, + "clientSecret": map[string]any{ + "create": "someSecret", + }, + }, + }} + _, err := helper.Connections.Resource.Create(ctx, connection, createOptions) + require.Error(t, err, "failed to create resource") + assert.Contains(t, err.Error(), "privateKey is forbidden in Gitlab connection") + }) +} diff --git a/pkg/tests/apis/provisioning/helper_test.go b/pkg/tests/apis/provisioning/helper_test.go index 814c29ea11e..5f2ec6a3985 100644 --- a/pkg/tests/apis/provisioning/helper_test.go +++ b/pkg/tests/apis/provisioning/helper_test.go @@ -53,6 +53,7 @@ type provisioningTestHelper struct { ProvisioningPath string Repositories *apis.K8sResourceClient + Connections *apis.K8sResourceClient Jobs *apis.K8sResourceClient Folders *apis.K8sResourceClient DashboardsV0 *apis.K8sResourceClient @@ -703,6 +704,11 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper Namespace: "default", // actually org1 GVR: provisioning.RepositoryResourceInfo.GroupVersionResource(), }) + connections := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: "default", // actually org1 + GVR: provisioning.ConnectionResourceInfo.GroupVersionResource(), + }) jobs := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, Namespace: "default", // actually org1 @@ -763,6 +769,7 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper K8sTestHelper: helper, Repositories: repositories, + Connections: connections, AdminREST: adminClient, EditorREST: editorClient, ViewerREST: viewerClient, From e03f7fe8782aae98ce1bc8781c555512d112e6b6 Mon Sep 17 00:00:00 2001 From: "Marc M." <146180665+grafakus@users.noreply.github.com> Date: Tue, 16 Dec 2025 15:11:23 +0100 Subject: [PATCH 151/297] DynamicDashboards: prevent nested repeats based on the same variable (#114953) --- .../SceneGridRowEditableElement.tsx | 2 +- .../row-actions/RowActionsRenderer.tsx | 5 ++--- .../scene/layout-rows/RowItemEditor.tsx | 7 +++--- .../scene/layout-tabs/TabItemEditor.tsx | 7 +++--- .../RepeatRowSelect/RepeatRowSelect.tsx | 22 +++++++++++++------ 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/layout-default/SceneGridRowEditableElement.tsx b/public/app/features/dashboard-scene/scene/layout-default/SceneGridRowEditableElement.tsx index 3d89bb9a69a..68f07f4ceed 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/SceneGridRowEditableElement.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/SceneGridRowEditableElement.tsx @@ -107,7 +107,7 @@ function RowRepeatSelect({ row, dashboard, id }: { row: SceneGridRow; dashboard: <> { if (repeat) { diff --git a/public/app/features/dashboard-scene/scene/layout-default/row-actions/RowActionsRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-default/row-actions/RowActionsRenderer.tsx index 6a6215aff95..4eaaad95cc0 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/row-actions/RowActionsRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/row-actions/RowActionsRenderer.tsx @@ -8,7 +8,7 @@ import { Icon, useStyles2 } from '@grafana/ui'; import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard/constants'; import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; -import { getQueryRunnerFor, useDashboard, useDashboardState } from '../../../utils/utils'; +import { getQueryRunnerFor, useDashboardState } from '../../../utils/utils'; import { DashboardGridItem } from '../DashboardGridItem'; import { RowRepeaterBehavior } from '../RowRepeaterBehavior'; @@ -18,7 +18,6 @@ import { RowOptionsButton } from './RowOptionsButton'; export function RowActionsRenderer({ model }: SceneComponentProps) { const row = model.getParent(); const { title, children } = row.useState(); - const dashboard = useDashboard(model); const { meta, isEditing } = useDashboardState(model); const styles = useStyles2(getStyles); @@ -53,7 +52,7 @@ export function RowActionsRenderer({ model }: SceneComponentProps) { model.onUpdate(title, repeat)} isUsingDashboardDS={isUsingDashboardDS} /> diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx index 2df803da9fd..c15551dea04 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemEditor.tsx @@ -2,7 +2,7 @@ import { useId, useMemo, useRef } from 'react'; import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; -import { Alert, Input, Switch, TextLink, Field } from '@grafana/ui'; +import { Alert, Field, Input, Switch, TextLink } from '@grafana/ui'; import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; import { RepeatRowSelect2 } from 'app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect'; @@ -11,7 +11,7 @@ import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSou import { useConditionalRenderingEditor } from '../../conditional-rendering/hooks/useConditionalRenderingEditor'; import { dashboardEditActions } from '../../edit-pane/shared'; -import { getQueryRunnerFor, useDashboard } from '../../utils/utils'; +import { getQueryRunnerFor } from '../../utils/utils'; import { useLayoutCategory } from '../layouts-shared/DashboardLayoutSelector'; import { generateUniqueTitle, useEditPaneInputAutoFocus } from '../layouts-shared/utils'; @@ -128,7 +128,6 @@ function FillScreenSwitch({ row, id }: { row: RowItem; id?: string }) { function RowRepeatSelect({ row, id }: { row: RowItem; id?: string }) { const { layout } = row.useState(); - const dashboard = useDashboard(row); const isAnyPanelUsingDashboardDS = layout.getVizPanels().some((vizPanel) => { const runner = getQueryRunnerFor(vizPanel); @@ -143,7 +142,7 @@ function RowRepeatSelect({ row, id }: { row: RowItem; id?: string }) { <> row.onChangeRepeat(repeat)} /> diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx index ceb04d2fbe4..69f81584a48 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabItemEditor.tsx @@ -2,7 +2,7 @@ import { useMemo, useRef } from 'react'; import { selectors } from '@grafana/e2e-selectors'; import { Trans, t } from '@grafana/i18n'; -import { Alert, Input, Field, TextLink } from '@grafana/ui'; +import { Alert, Field, Input, TextLink } from '@grafana/ui'; import { OptionsPaneCategoryDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneCategoryDescriptor'; import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; import { RepeatRowSelect2 } from 'app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect'; @@ -11,7 +11,7 @@ import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSou import { useConditionalRenderingEditor } from '../../conditional-rendering/hooks/useConditionalRenderingEditor'; import { dashboardEditActions } from '../../edit-pane/shared'; -import { getQueryRunnerFor, useDashboard } from '../../utils/utils'; +import { getQueryRunnerFor } from '../../utils/utils'; import { useLayoutCategory } from '../layouts-shared/DashboardLayoutSelector'; import { generateUniqueTitle, useEditPaneInputAutoFocus } from '../layouts-shared/utils'; @@ -99,7 +99,6 @@ function TabTitleInput({ tab, isNewElement, id }: { tab: TabItem; isNewElement: function TabRepeatSelect({ tab, id }: { tab: TabItem; id?: string }) { const { layout } = tab.useState(); - const dashboard = useDashboard(tab); const isAnyPanelUsingDashboardDS = layout.getVizPanels().some((vizPanel) => { const runner = getQueryRunnerFor(vizPanel); @@ -114,7 +113,7 @@ function TabRepeatSelect({ tab, id }: { tab: TabItem; id?: string }) { <> tab.onChangeRepeat(repeat)} /> diff --git a/public/app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect.tsx b/public/app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect.tsx index de68a822e3a..94cbecec75c 100644 --- a/public/app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect.tsx +++ b/public/app/features/dashboard/components/RepeatRowSelect/RepeatRowSelect.tsx @@ -2,7 +2,7 @@ import { useCallback, useMemo } from 'react'; import { SelectableValue } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { SceneObject, sceneGraph } from '@grafana/scenes'; +import { LocalValueVariable, SceneObject, sceneGraph } from '@grafana/scenes'; import { Combobox, ComboboxOption, Select } from '@grafana/ui'; import { useSelector } from 'app/types/store'; @@ -59,10 +59,18 @@ export const RepeatRowSelect2 = ({ sceneContext, repeat, id, onChange }: Props2) const variables = sceneVars.useState().variables; const variableOptions = useMemo(() => { - const options: ComboboxOption[] = variables.map((item) => ({ - label: item.state.name, - value: item.state.name, - })); + const options: ComboboxOption[] = variables + .filter((item) => { + if (sceneContext.parent) { + // filter out local value variables (which are only set on repeated items) + return !(sceneGraph.lookupVariable(item.state.name, sceneContext.parent) instanceof LocalValueVariable); + } + return true; + }) + .map((item) => ({ + label: item.state.name, + value: item.state.name, + })); options.unshift({ label: t('dashboard.repeat-row-select2.variable-options.label.disable-repeating', 'Disable repeating'), @@ -70,7 +78,7 @@ export const RepeatRowSelect2 = ({ sceneContext, repeat, id, onChange }: Props2) }); return options; - }, [variables]); + }, [sceneContext, variables]); const onSelectChange = useCallback((value: ComboboxOption | null) => value && onChange(value.value), [onChange]); @@ -79,7 +87,7 @@ export const RepeatRowSelect2 = ({ sceneContext, repeat, id, onChange }: Props2) return ( Date: Tue, 16 Dec 2025 09:33:54 -0500 Subject: [PATCH 152/297] Dashboards: exit edit mode after saving changes modal (#115380) Exit edit mode with unsaved changes --- .../scene/DashboardScene.test.tsx | 35 +++++++++++++++++++ .../dashboard-scene/scene/DashboardScene.tsx | 6 +++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx index ebe63e8e43e..b6bd3c2c4ca 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx @@ -30,8 +30,10 @@ import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; import { DashboardEventAction } from 'app/features/live/dashboard/types'; import { VariablesChanged } from 'app/features/variables/types'; +import { ShowConfirmModalEvent } from 'app/types/events'; import { buildPanelEditScene } from '../panel-edit/PanelEditor'; +import { SaveDashboardDrawer } from '../saving/SaveDashboardDrawer'; import { createWorker } from '../saving/createDetectChangesWorker'; import { buildGridItemForPanel, transformSaveModelToScene } from '../serialization/transformSaveModelToScene'; import { DecoratedRevisionModel } from '../settings/VersionsEditView'; @@ -39,6 +41,7 @@ import { historySrv } from '../settings/version-history/HistorySrv'; import { getCloneKey } from '../utils/clone'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { findVizPanelByKey, getLibraryPanelBehavior, isLibraryPanel } from '../utils/utils'; +import * as utils from '../utils/utils'; import { DashboardControls } from './DashboardControls'; import { DashboardScene, DashboardSceneState } from './DashboardScene'; @@ -162,6 +165,38 @@ describe('DashboardScene', () => { expect(scene.state.meta.version).toEqual(2); }); + it('Should exit edit mode after saving from unsaved changes modal when dashboardNewLayouts is enabled', () => { + const originalFeatureToggle = config.featureToggles.dashboardNewLayouts; + config.featureToggles.dashboardNewLayouts = true; + + const publishSpy = jest.spyOn(appEvents, 'publish'); + const hasActualSaveChangesSpy = jest.spyOn(utils, 'hasActualSaveChanges').mockReturnValue(true); + + scene.setState({ title: 'Updated title' }); + expect(scene.state.isDirty).toBe(true); + scene.exitEditMode({ skipConfirm: false }); + + const modalCall = publishSpy.mock.calls.find((call) => call[0] instanceof ShowConfirmModalEvent); + expect(modalCall).toBeDefined(); + + const modalEvent = modalCall![0] as ShowConfirmModalEvent; + expect(modalEvent.payload.altActionText).toBeDefined(); + + modalEvent.payload.onAltAction?.(); + + expect(scene.state.overlay).toBeDefined(); + + const overlay = scene.state.overlay as SaveDashboardDrawer; + expect(overlay.state.onSaveSuccess).toBeDefined(); + + overlay.state.onSaveSuccess!(); + expect(scene.state.isEditing).toBe(false); + + publishSpy.mockRestore(); + hasActualSaveChangesSpy.mockRestore(); + config.featureToggles.dashboardNewLayouts = originalFeatureToggle; + }); + it('Should start the detect changes worker', () => { expect(worker.onmessage).toBeDefined(); }); diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 00655036d04..82a20dbcd52 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -335,7 +335,11 @@ export class DashboardScene extends SceneObjectBase impleme yesText: t('dashboard-scene.dashboard-scene.modal.discard', 'Discard'), yesButtonVariant: 'destructive', onAltAction: () => { - this.openSaveDrawer({}); + this.openSaveDrawer({ + onSaveSuccess: () => { + this.exitEditModeConfirmed(false); + }, + }); }, onConfirm: () => { this.exitEditModeConfirmed(); From 321e60e69c19c78a5e40169d1ec20c67c6ac89f7 Mon Sep 17 00:00:00 2001 From: Kristina Demeshchik Date: Tue, 16 Dec 2025 09:34:10 -0500 Subject: [PATCH 153/297] Dashboards: Add missing keyboard shortcuts for new layouts (#115377) * missing key bindings * Collapse repeted panels --- .../dashboard-keybindings.spec.ts | 59 +++++++++++++++++++ .../scene/keyboardShortcuts.ts | 5 ++ .../scene/layout-rows/RowsLayoutManager.tsx | 26 ++++++++ 3 files changed, 90 insertions(+) create mode 100644 e2e-playwright/dashboard-new-layouts/dashboard-keybindings.spec.ts diff --git a/e2e-playwright/dashboard-new-layouts/dashboard-keybindings.spec.ts b/e2e-playwright/dashboard-new-layouts/dashboard-keybindings.spec.ts new file mode 100644 index 00000000000..19ad38f16d5 --- /dev/null +++ b/e2e-playwright/dashboard-new-layouts/dashboard-keybindings.spec.ts @@ -0,0 +1,59 @@ +import { test, expect } from '@grafana/plugin-e2e'; + +test.use({ + featureToggles: { + kubernetesDashboards: true, + dashboardNewLayouts: true, + }, +}); + +test.describe('Dashboard keybindings with new layouts', { tag: ['@dashboards'] }, () => { + test.use({ + viewport: { width: 1280, height: 1080 }, + }); + + test('should collapse and expand all rows', async ({ gotoDashboardPage, page, selectors }) => { + const dashboardPage = await gotoDashboardPage({ uid: 'Repeating-rows-uid/repeating-rows' }); + + const panelContents = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.content); + await expect(panelContents).toHaveCount(5); + await expect( + dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('server = A, pod = Bob')) + ).toBeVisible(); + await expect( + dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('server = B, pod = Bob')) + ).toBeVisible(); + + // Collapse all rows using keyboard shortcut: d + Shift+C + await page.keyboard.press('d'); + await page.keyboard.press('Shift+C'); + + await expect(panelContents).toHaveCount(0); + await expect(page.getByText('server = A, pod = Bob')).toBeHidden(); + await expect(page.getByText('server = B, pod = Bob')).toBeHidden(); + + // Expand all rows using keyboard shortcut: d + Shift+E + await page.keyboard.press('d'); + await page.keyboard.press('Shift+E'); + + await expect(panelContents).toHaveCount(6); + await expect(page.getByText('server = A, pod = Bob')).toBeVisible(); + await expect(page.getByText('server = B, pod = Bob')).toBeVisible(); + }); + + test('should open panel inspect', async ({ gotoDashboardPage, page, selectors }) => { + const dashboardPage = await gotoDashboardPage({ uid: 'edediimbjhdz4b/a-tall-dashboard' }); + + // Find Panel #1 and press 'i' to open inspector + const panel1 = dashboardPage.getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel #1')); + await expect(panel1).toBeVisible(); + await panel1.press('i'); + + await expect(dashboardPage.getByGrafanaSelector(selectors.components.PanelInspector.Json.content)).toBeVisible(); + + // Press Escape to close inspector + await page.keyboard.press('Escape'); + + await expect(page.getByTestId(selectors.components.PanelInspector.Json.content)).toBeHidden(); + }); +}); diff --git a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts index f1878aeb466..cb5172a0e7e 100644 --- a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts +++ b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts @@ -18,6 +18,7 @@ import { getPanelIdForVizPanel } from '../utils/utils'; import { DashboardScene } from './DashboardScene'; import { onRemovePanel, toggleVizPanelLegend } from './PanelMenuBehavior'; import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager'; +import { RowsLayoutManager } from './layout-rows/RowsLayoutManager'; export function setupKeyboardShortcuts(scene: DashboardScene) { const keybindings = new KeybindingSet(); @@ -265,6 +266,8 @@ export function setupKeyboardShortcuts(scene: DashboardScene) { onTrigger: () => { if (scene.state.body instanceof DefaultGridLayoutManager) { scene.state.body.collapseAllRows(); + } else if (scene.state.body instanceof RowsLayoutManager) { + scene.state.body.collapseAllRows(); } }, }); @@ -275,6 +278,8 @@ export function setupKeyboardShortcuts(scene: DashboardScene) { onTrigger: () => { if (scene.state.body instanceof DefaultGridLayoutManager) { scene.state.body.expandAllRows(); + } else if (scene.state.body instanceof RowsLayoutManager) { + scene.state.body.expandAllRows(); } }, }); diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx index 778aad91757..48f11357e24 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowsLayoutManager.tsx @@ -390,4 +390,30 @@ export class RowsLayoutManager extends SceneObjectBase i return duplicateTitles; } + + public collapseAllRows() { + this.state.rows.forEach((row) => { + if (!row.getCollapsedState()) { + row.setCollapsedState(true); + } + row.state.repeatedRows?.forEach((repeatedRow) => { + if (!repeatedRow.getCollapsedState()) { + repeatedRow.setCollapsedState(true); + } + }); + }); + } + + public expandAllRows() { + this.state.rows.forEach((row) => { + if (row.getCollapsedState()) { + row.setCollapsedState(false); + } + row.state.repeatedRows?.forEach((repeatedRow) => { + if (repeatedRow.getCollapsedState()) { + repeatedRow.setCollapsedState(false); + } + }); + }); + } } From 52205fbf4fabf16a669acda2f4e7de974c9e465d Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Tue, 16 Dec 2025 15:56:16 +0100 Subject: [PATCH 154/297] CustomVariable: Remove dead code and incorrect label (#115340) * CustomVariable: Remove dead code and incorrect label * Update i18n translations --- .../CustomVariableEditor.tsx | 20 +------------------ .../getCustomVariableOptions.tsx | 2 -- public/locales/en-US/grafana.json | 3 +-- 3 files changed, 2 insertions(+), 23 deletions(-) diff --git a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/CustomVariableEditor.tsx b/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/CustomVariableEditor.tsx index 8dfc7b3eac1..1d73d5a6f2b 100644 --- a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/CustomVariableEditor.tsx +++ b/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/CustomVariableEditor.tsx @@ -1,13 +1,9 @@ import { FormEvent, useCallback } from 'react'; -import { t } from '@grafana/i18n'; -import { CustomVariable, SceneVariable } from '@grafana/scenes'; +import { CustomVariable } from '@grafana/scenes'; -import { OptionsPaneItemDescriptor } from '../../../../../dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; import { CustomVariableForm } from '../../components/CustomVariableForm'; -import { PaneItem } from './PaneItem'; - interface CustomVariableEditorProps { variable: CustomVariable; onRunQuery: () => void; @@ -67,17 +63,3 @@ export function CustomVariableEditor({ variable, onRunQuery }: CustomVariableEdi /> ); } - -export function getCustomVariableOptions(variable: SceneVariable): OptionsPaneItemDescriptor[] { - if (!(variable instanceof CustomVariable)) { - return []; - } - - return [ - new OptionsPaneItemDescriptor({ - title: t('dashboard.edit-pane.variable.custom-options.values', 'Values separated by comma'), - id: 'custom-variable-values', - render: ({ props }) => , - }), - ]; -} diff --git a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/getCustomVariableOptions.tsx b/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/getCustomVariableOptions.tsx index 5033ebb1407..7dea9f83ec3 100644 --- a/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/getCustomVariableOptions.tsx +++ b/public/app/features/dashboard-scene/settings/variables/editors/CustomVariableEditor/getCustomVariableOptions.tsx @@ -1,4 +1,3 @@ -import { t } from '@grafana/i18n'; import { CustomVariable, SceneVariable } from '@grafana/scenes'; import { OptionsPaneItemDescriptor } from '../../../../../dashboard/components/PanelEditor/OptionsPaneItemDescriptor'; @@ -12,7 +11,6 @@ export function getCustomVariableOptions(variable: SceneVariable): OptionsPaneIt return [ new OptionsPaneItemDescriptor({ - title: t('dashboard.edit-pane.variable.custom-options.values', 'Values separated by comma'), id: 'custom-variable-values', render: ({ props }) => , }), diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index b3b6ce2c8db..eef8a1bdd7d 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -4821,8 +4821,7 @@ "apply": "Apply", "change-value": "Change variable value", "discard": "Discard", - "modal-title": "Custom Variable", - "values": "Values separated by comma" + "modal-title": "Custom Variable" }, "datasource-options": { "name-filter": "Name filter", From c09cb08deccef2f56d0e85acbdc813dd473c84c1 Mon Sep 17 00:00:00 2001 From: Misi Date: Tue, 16 Dec 2025 16:48:38 +0100 Subject: [PATCH 155/297] Teams: Refactor TeamGroupSync UI to functional component and hooks (#115223) * Refactor TeamGroupSync to use func component and hooks * Fix tests, lint * Lint prune suppressions * Address feedback * Address feedback - tests --- eslint-suppressions.json | 5 - .../src/clients/rtkq/legacy/endpoints.gen.ts | 6 +- .../grafana-test-utils/src/fixtures/teams.ts | 6 +- .../src/handlers/api/teams/handlers.ts | 28 +- packages/grafana-test-utils/src/unstable.ts | 2 +- public/api-enterprise-spec.json | 9 +- public/api-merged.json | 9 +- public/app/core/reducers/root.ts | 2 - .../app/features/teams/TeamGroupSync.test.tsx | 48 +-- public/app/features/teams/TeamGroupSync.tsx | 290 ++++++++---------- public/app/features/teams/mocks/teamMocks.ts | 15 +- public/app/features/teams/state/actions.ts | 26 -- public/app/features/teams/state/reducers.ts | 25 -- public/app/features/teams/state/selectors.ts | 3 - public/app/types/teams.ts | 4 - public/openapi3.json | 9 +- 16 files changed, 198 insertions(+), 289 deletions(-) delete mode 100644 public/app/features/teams/state/actions.ts delete mode 100644 public/app/features/teams/state/reducers.ts delete mode 100644 public/app/features/teams/state/selectors.ts diff --git a/eslint-suppressions.json b/eslint-suppressions.json index e98c47ddfac..2e7b5712911 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -3107,11 +3107,6 @@ "count": 2 } }, - "public/app/features/teams/TeamGroupSync.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "public/app/features/templating/fieldAccessorCache.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 diff --git a/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts index f614aed35a8..4f17bb46ecf 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts @@ -3440,16 +3440,16 @@ export type RemoveTeamGroupApiQueryApiResponse = /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; export type RemoveTeamGroupApiQueryApiArg = { groupId?: string; - teamId: number; + teamId: string; }; export type GetTeamGroupsApiApiResponse = /** status 200 (empty) */ TeamGroupDto[]; export type GetTeamGroupsApiApiArg = { - teamId: number; + teamId: string; }; export type AddTeamGroupApiApiResponse = /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; export type AddTeamGroupApiApiArg = { - teamId: number; + teamId: string; teamGroupMapping: TeamGroupMapping; }; export type SearchTeamGroupsApiResponse = /** status 200 (empty) */ SearchTeamGroupsQueryResult; diff --git a/packages/grafana-test-utils/src/fixtures/teams.ts b/packages/grafana-test-utils/src/fixtures/teams.ts index 4039129fb5d..e12cee069dd 100644 --- a/packages/grafana-test-utils/src/fixtures/teams.ts +++ b/packages/grafana-test-utils/src/fixtures/teams.ts @@ -24,13 +24,15 @@ export const MOCK_TEAMS = [ }, ]; +export const MOCK_TEAM_GROUPS = [{ groupId: 'cn=users,ou=groups,dc=grafana,dc=org' }, { groupId: 'another-group' }]; + export const setupMockTeams = () => { mockTeamsMap.clear(); MOCK_TEAMS.forEach((team) => { - mockTeamsMap.set(team.metadata.name, { team, groups: [] }); + mockTeamsMap.set(team.metadata.name, { team, groups: [...MOCK_TEAM_GROUPS] }); }); }; export const mockTeamsMap = new Map }>( - MOCK_TEAMS.map((team) => [team.metadata.name, { team, groups: [] }]) + MOCK_TEAMS.map((team) => [team.metadata.name, { team, groups: [...MOCK_TEAM_GROUPS] }]) ); diff --git a/packages/grafana-test-utils/src/handlers/api/teams/handlers.ts b/packages/grafana-test-utils/src/handlers/api/teams/handlers.ts index c8a42bf9bba..55cbf7a5621 100644 --- a/packages/grafana-test-utils/src/handlers/api/teams/handlers.ts +++ b/packages/grafana-test-utils/src/handlers/api/teams/handlers.ts @@ -79,7 +79,7 @@ const teamsGroupsHandler = () => return HttpResponse.json(team.groups); }); -const teamsUpdateGroupsHandler = () => +const teamsAddGroupHandler = () => http.post<{ uid: string }, { groupId: string }>('/api/teams/:uid/groups', async ({ params, request }) => { const teamData = mockTeamsMap.get(params.uid); const body = await request.json(); @@ -95,6 +95,29 @@ const teamsUpdateGroupsHandler = () => return HttpResponse.json({ message: 'Group added to Team' }); }); +const teamsRemoveGroupHandler = () => + http.delete<{ uid: string }>('/api/teams/:uid/groups', async ({ params, request }) => { + const teamData = mockTeamsMap.get(params.uid); + const url = new URL(request.url); + const groupId = url.searchParams.get('groupId'); + + if (!teamData) { + return HttpResponse.json({ message: 'Not found' }, { status: 404 }); + } + + if (!groupId) { + return HttpResponse.json({ message: 'Missing groupId' }, { status: 400 }); + } + + const updatedTeam = { + ...teamData, + groups: teamData.groups.filter((g) => g.groupId !== groupId), + }; + mockTeamsMap.set(params.uid, updatedTeam); + + return HttpResponse.json({ message: 'Group removed from Team' }); + }); + const searchTeamsHandler = () => http.get('/api/teams/search', async ({ request }) => { const url = new URL(request.url); @@ -150,7 +173,8 @@ const updateTeamHandler = () => const handlers = [ teamsPreferencesHandler(), teamsGroupsHandler(), - teamsUpdateGroupsHandler(), + teamsAddGroupHandler(), + teamsRemoveGroupHandler(), searchTeamsHandler(), getTeamHandler(), deleteTeamHandler(), diff --git a/packages/grafana-test-utils/src/unstable.ts b/packages/grafana-test-utils/src/unstable.ts index 8758e294464..d03bc685d9e 100644 --- a/packages/grafana-test-utils/src/unstable.ts +++ b/packages/grafana-test-utils/src/unstable.ts @@ -1,4 +1,4 @@ import { wellFormedTree } from './fixtures/folders'; export const getFolderFixtures = wellFormedTree; -export { MOCK_TEAMS } from './fixtures/teams'; +export { MOCK_TEAMS, MOCK_TEAM_GROUPS } from './fixtures/teams'; diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index d2fdd854168..9f09c0135d7 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -2263,8 +2263,7 @@ "operationId": "getTeamGroupsApi", "parameters": [ { - "type": "integer", - "format": "int64", + "type": "string", "name": "teamId", "in": "path", "required": true @@ -2308,8 +2307,7 @@ } }, { - "type": "integer", - "format": "int64", + "type": "string", "name": "teamId", "in": "path", "required": true @@ -2350,8 +2348,7 @@ "in": "query" }, { - "type": "integer", - "format": "int64", + "type": "string", "name": "teamId", "in": "path", "required": true diff --git a/public/api-merged.json b/public/api-merged.json index 570c4c2687d..323a545e0cf 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -9927,8 +9927,7 @@ "operationId": "getTeamGroupsApi", "parameters": [ { - "type": "integer", - "format": "int64", + "type": "string", "name": "teamId", "in": "path", "required": true @@ -9972,8 +9971,7 @@ } }, { - "type": "integer", - "format": "int64", + "type": "string", "name": "teamId", "in": "path", "required": true @@ -10014,8 +10012,7 @@ "in": "query" }, { - "type": "integer", - "format": "int64", + "type": "string", "name": "teamId", "in": "path", "required": true diff --git a/public/app/core/reducers/root.ts b/public/app/core/reducers/root.ts index d86b93c916e..b73e12e2f22 100644 --- a/public/app/core/reducers/root.ts +++ b/public/app/core/reducers/root.ts @@ -23,7 +23,6 @@ import { reducer as pluginsReducer } from 'app/features/plugins/admin/state/redu import userReducers from 'app/features/profile/state/reducers'; import serviceAccountsReducer from 'app/features/serviceaccounts/state/reducers'; import supportBundlesReducer from 'app/features/support-bundles/state/reducers'; -import teamsReducers from 'app/features/teams/state/reducers'; import usersReducers from 'app/features/users/state/reducers'; import templatingReducers from 'app/features/variables/state/keyedVariablesReducer'; @@ -33,7 +32,6 @@ import { cleanUpAction } from '../actions/cleanUp'; const rootReducers = { ...sharedReducers, ...alertingReducers, - ...teamsReducers, ...dashboardReducers, ...exploreReducers, ...dataSourcesReducers, diff --git a/public/app/features/teams/TeamGroupSync.test.tsx b/public/app/features/teams/TeamGroupSync.test.tsx index d3a83d9c5dc..8f0e98eda4c 100644 --- a/public/app/features/teams/TeamGroupSync.test.tsx +++ b/public/app/features/teams/TeamGroupSync.test.tsx @@ -1,26 +1,17 @@ -import { render, screen } from 'test/test-utils'; +import { render, screen, waitFor } from 'test/test-utils'; import { setBackendSrv } from '@grafana/runtime'; import { setupMockServer } from '@grafana/test-utils/server'; -import { MOCK_TEAMS } from '@grafana/test-utils/unstable'; +import { MOCK_TEAMS, MOCK_TEAM_GROUPS } from '@grafana/test-utils/unstable'; import { backendSrv } from 'app/core/services/backend_srv'; -import { TeamGroup, TeamState } from 'app/types/teams'; import TeamGroupSync from './TeamGroupSync'; -import { getMockTeamGroups } from './mocks/teamMocks'; setBackendSrv(backendSrv); setupMockServer(); -const setup = (preloadedTeamState?: Partial) => { - return render(, { - preloadedState: { - team: { - groups: [], - ...preloadedTeamState, - }, - }, - }); +const setup = () => { + return render(); }; describe('TeamGroupSync', () => { @@ -29,28 +20,37 @@ describe('TeamGroupSync', () => { expect(screen.getByRole('heading', { name: /External group sync/i })).toBeInTheDocument(); }); - it('should render groups table', () => { - setup({ groups: getMockTeamGroups(3) }); - expect(screen.getAllByRole('row')).toHaveLength(4); // 3 items plus table header + it('should render groups table', async () => { + setup(); + expect(await screen.findAllByRole('row')).toHaveLength(MOCK_TEAM_GROUPS.length + 1); // items plus table header }); it('should call add group', async () => { const { user } = setup(); - // Empty List CTA "Add group" button is second in the DOM order - await user.click(screen.getAllByRole('button', { name: /add group/i })[1]); + // Wait for the groups to load so the "Add group" button appears + await screen.findAllByRole('row'); + + await user.click(screen.getAllByRole('button', { name: /add group/i })[0]); expect(screen.getByRole('textbox', { name: /add external group/i })).toBeVisible(); await user.type(screen.getByRole('textbox', { name: /add external group/i }), 'test/group'); - await user.click(screen.getAllByRole('button', { name: /add group/i })[0]); + await user.click(screen.getAllByRole('button', { name: /add group/i })[1]); - expect(screen.getByRole('row', { name: /test\/group/i })).toBeInTheDocument(); + expect(await screen.findByRole('row', { name: /test\/group/i })).toBeInTheDocument(); }); it('should remove group', async () => { - const mockGroup: TeamGroup = { teamId: 1, groupId: 'someGroup' }; - const { user } = setup({ groups: [mockGroup] }); - await user.click(screen.getByRole('button', { name: 'Remove group someGroup' })); + const { user } = setup(); + const groupToRemove = MOCK_TEAM_GROUPS[0].groupId; - expect(screen.queryByRole('row', { name: /test\/group/i })).not.toBeInTheDocument(); + // Wait for group to be rendered + await screen.findByRole('row', { name: new RegExp(groupToRemove, 'i') }); + + // Remove group + await user.click(screen.getByRole('button', { name: `Remove group ${groupToRemove}` })); + + await waitFor(() => + expect(screen.queryByRole('row', { name: new RegExp(groupToRemove, 'i') })).not.toBeInTheDocument() + ); }); }); diff --git a/public/app/features/teams/TeamGroupSync.tsx b/public/app/features/teams/TeamGroupSync.tsx index 37887ee8d6a..66a227527dc 100644 --- a/public/app/features/teams/TeamGroupSync.tsx +++ b/public/app/features/teams/TeamGroupSync.tsx @@ -1,85 +1,63 @@ import { css, cx } from '@emotion/css'; -import { FormEventHandler, PureComponent } from 'react'; -import { connect, ConnectedProps } from 'react-redux'; +import { FormEventHandler, useState } from 'react'; +import { + TeamGroupDto, + useAddTeamGroupApiMutation, + useGetTeamGroupsApiQuery, + useRemoveTeamGroupApiQueryMutation, +} from '@grafana/api-clients/rtkq/legacy'; import { Trans, t } from '@grafana/i18n'; -import { Input, Tooltip, Icon, Button, useTheme2, InlineField, InlineFieldRow } from '@grafana/ui'; +import { Input, Tooltip, Icon, Button, useTheme2, InlineField, InlineFieldRow, useStyles2 } from '@grafana/ui'; import { SlideDown } from 'app/core/components/Animations/SlideDown'; import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import { UpgradeBox, UpgradeContent, UpgradeContentProps } from 'app/core/components/Upgrade/UpgradeBox'; import { highlightTrial } from 'app/features/admin/utils'; -import { StoreState } from 'app/types/store'; -import { TeamGroup } from 'app/types/teams'; -import { addTeamGroup, loadTeamGroups, removeTeamGroup } from './state/actions'; -import { getTeamGroups } from './state/selectors'; - -function mapStateToProps(state: StoreState) { - return { - groups: getTeamGroups(state.team), - }; -} - -const mapDispatchToProps = { - loadTeamGroups, - addTeamGroup, - removeTeamGroup, -}; - -interface OwnProps { +interface Props { isReadOnly: boolean; teamUid: string; } -interface State { - isAdding: boolean; - newGroupId: string; -} - -const connector = connect(mapStateToProps, mapDispatchToProps); -export type Props = OwnProps & ConnectedProps; - const headerTooltip = `Sync LDAP, OAuth or SAML groups with your Grafana teams.`; -export class TeamGroupSync extends PureComponent { - constructor(props: Props) { - super(props); - this.state = { isAdding: false, newGroupId: '' }; - } +export const TeamGroupSync = ({ isReadOnly, teamUid }: Props) => { + const [isAddBoxVisible, setIsAddBoxVisible] = useState(false); + const [newGroupId, setNewGroupId] = useState(''); + const styles = useStyles2(getStyles); - componentDidMount() { - this.fetchTeamGroups(); - } + const { data: groups = [] } = useGetTeamGroupsApiQuery({ teamId: teamUid }); + const [addTeamGroup] = useAddTeamGroupApiMutation(); + const [removeTeamGroup] = useRemoveTeamGroupApiQueryMutation(); - async fetchTeamGroups() { - this.props.loadTeamGroups(this.props.teamUid); - } - - onToggleAdding = () => { - this.setState({ isAdding: !this.state.isAdding }); + const onToggleAdding = () => { + setIsAddBoxVisible(!isAddBoxVisible); }; - onNewGroupIdChanged: FormEventHandler = (event) => { - this.setState({ newGroupId: event.currentTarget.value }); + const onNewGroupIdChanged: FormEventHandler = (event) => { + setNewGroupId(event.currentTarget.value); }; - onAddGroup: FormEventHandler = (event) => { + const onAddGroup: FormEventHandler = async (event) => { event.preventDefault(); - this.props.addTeamGroup(this.props.teamUid, this.state.newGroupId); - this.setState({ isAdding: false, newGroupId: '' }); + await addTeamGroup({ teamId: teamUid, teamGroupMapping: { groupId: newGroupId } }); + setIsAddBoxVisible(false); + setNewGroupId(''); }; - onRemoveGroup = (group: TeamGroup) => { - this.props.removeTeamGroup(this.props.teamUid, group.groupId); + const onRemoveGroup = async (groupId: string | undefined) => { + if (!groupId) { + return; + } + await removeTeamGroup({ teamId: teamUid, groupId }); }; - isNewGroupValid() { - return this.state.newGroupId.length > 1; - } + const isNewGroupValid = () => { + return newGroupId.length > 1; + }; - renderGroup(group: TeamGroup) { - const { isReadOnly } = this.props; + const renderGroup = (group: TeamGroupDto) => { return ( {group.groupId} @@ -87,7 +65,7 @@ export class TeamGroupSync extends PureComponent { - )} -
- - -
- -
- - - - - - -
-
-
- - {groups.length === 0 && - !isAdding && - (highlightTrial() ? ( - - ) : ( - - ))} - +
{groups.length > 0 && ( -
- - - - - - - {groups.map((group) => this.renderGroup(group))} -
- External Group ID - -
-
+ )}
- ); - } -} + + +
+ +
+ + + + + + +
+
+
+ + {groups.length === 0 && + !isAddBoxVisible && + (highlightTrial() ? ( + + ) : ( + + ))} + + {groups.length > 0 && ( +
+ + + + + + + {groups.map((group) => renderGroup(group))} +
+ External Group ID + +
+
+ )} +
+ ); +}; export const TeamSyncUpgradeContent = ({ action }: { action?: UpgradeContentProps['action'] }) => { const theme = useTheme2(); @@ -226,7 +196,7 @@ export const TeamSyncUpgradeContent = ({ action }: { action?: UpgradeContentProp /> ); }; -export default connect(mapStateToProps, mapDispatchToProps)(TeamGroupSync); +export default TeamGroupSync; const getStyles = () => ({ icon: css({ diff --git a/public/app/features/teams/mocks/teamMocks.ts b/public/app/features/teams/mocks/teamMocks.ts index d6472499cb2..d43f1c454b2 100644 --- a/public/app/features/teams/mocks/teamMocks.ts +++ b/public/app/features/teams/mocks/teamMocks.ts @@ -1,7 +1,7 @@ import { randomBytes } from 'crypto'; import { TeamPermissionLevel } from 'app/types/acl'; -import { Team, TeamMember, TeamGroup } from 'app/types/teams'; +import { Team, TeamMember } from 'app/types/teams'; function generateShortUid(): string { return randomBytes(3).toString('hex'); // Generate a short UID @@ -44,16 +44,3 @@ export const getMockTeamMember = (): TeamMember => { permission: TeamPermissionLevel.Member, }; }; - -export const getMockTeamGroups = (amount: number): TeamGroup[] => { - const groups: TeamGroup[] = []; - - for (let i = 1; i <= amount; i++) { - groups.push({ - groupId: `group-${i}`, - teamId: 1, - }); - } - - return groups; -}; diff --git a/public/app/features/teams/state/actions.ts b/public/app/features/teams/state/actions.ts deleted file mode 100644 index d0d05e0946c..00000000000 --- a/public/app/features/teams/state/actions.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { getBackendSrv } from '@grafana/runtime'; -import { ThunkResult } from 'app/types/store'; - -import { teamGroupsLoaded } from './reducers'; - -export function loadTeamGroups(teamUid: string): ThunkResult { - return async (dispatch) => { - const response = await getBackendSrv().get(`/api/teams/${teamUid}/groups`); - dispatch(teamGroupsLoaded(response)); - }; -} - -export function addTeamGroup(teamUid: string, groupId: string): ThunkResult { - return async (dispatch) => { - await getBackendSrv().post(`/api/teams/${teamUid}/groups`, { groupId: groupId }); - dispatch(loadTeamGroups(teamUid)); - }; -} - -export function removeTeamGroup(teamUid: string, groupId: string): ThunkResult { - return async (dispatch) => { - // need to use query parameter due to escaped characters in the request - await getBackendSrv().delete(`/api/teams/${teamUid}/groups?groupId=${encodeURIComponent(groupId)}`); - dispatch(loadTeamGroups(teamUid)); - }; -} diff --git a/public/app/features/teams/state/reducers.ts b/public/app/features/teams/state/reducers.ts deleted file mode 100644 index 50011316a72..00000000000 --- a/public/app/features/teams/state/reducers.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; - -import { TeamState, TeamGroup } from 'app/types/teams'; - -export const initialTeamState: TeamState = { - groups: [], -}; - -const teamSlice = createSlice({ - name: 'team', - initialState: initialTeamState, - reducers: { - teamGroupsLoaded: (state, action: PayloadAction): TeamState => { - return { ...state, groups: action.payload }; - }, - }, -}); - -export const { teamGroupsLoaded } = teamSlice.actions; - -export const teamReducer = teamSlice.reducer; - -export default { - team: teamReducer, -}; diff --git a/public/app/features/teams/state/selectors.ts b/public/app/features/teams/state/selectors.ts deleted file mode 100644 index 4ada9ee8d46..00000000000 --- a/public/app/features/teams/state/selectors.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { TeamState } from 'app/types/teams'; - -export const getTeamGroups = (state: TeamState) => state.groups; diff --git a/public/app/types/teams.ts b/public/app/types/teams.ts index 72202a21295..e78a74a3bda 100644 --- a/public/app/types/teams.ts +++ b/public/app/types/teams.ts @@ -37,7 +37,3 @@ export interface TeamGroup { groupId: string; teamId: number; } - -export interface TeamState { - groups: TeamGroup[]; -} diff --git a/public/openapi3.json b/public/openapi3.json index 3257a516bb0..9978e3dfa78 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -24691,8 +24691,7 @@ "name": "teamId", "required": true, "schema": { - "format": "int64", - "type": "integer" + "type": "string" } } ], @@ -24730,8 +24729,7 @@ "name": "teamId", "required": true, "schema": { - "format": "int64", - "type": "integer" + "type": "string" } } ], @@ -24769,8 +24767,7 @@ "name": "teamId", "required": true, "schema": { - "format": "int64", - "type": "integer" + "type": "string" } } ], From 6eab74a90fa2a73f66a2ced0d31ce77749086db8 Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Tue, 16 Dec 2025 16:59:40 +0100 Subject: [PATCH 156/297] Plugin Extensions: Remove react API refs in ComponentWithMeta (#115347) * maybe better way to attach meta? * chore(plugin-extensions): remove todo comment --------- Co-authored-by: Ashley Harrison --- .../extensions/usePluginComponents.tsx | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/public/app/features/plugins/extensions/usePluginComponents.tsx b/public/app/features/plugins/extensions/usePluginComponents.tsx index c800bf08a31..c7f5b40815f 100644 --- a/public/app/features/plugins/extensions/usePluginComponents.tsx +++ b/public/app/features/plugins/extensions/usePluginComponents.tsx @@ -67,21 +67,15 @@ export function createComponentWithMeta( ): ComponentTypeWithExtensionMeta { const { component: Component, ...config } = registryItem; - function ComponentWithMeta(props: Props) { - return ; - } - - ComponentWithMeta.displayName = Component.displayName; - ComponentWithMeta.defaultProps = Component.defaultProps; - ComponentWithMeta.propTypes = Component.propTypes; - ComponentWithMeta.contextTypes = Component.contextTypes; - ComponentWithMeta.meta = { - pluginId: config.pluginId, - title: config.title ?? '', - description: config.description ?? '', - id: generateExtensionId(config.pluginId, extensionPointId, config.title), - type: PluginExtensionTypes.component, - } satisfies PluginExtensionComponentMeta; + const ComponentWithMeta: ComponentTypeWithExtensionMeta = Object.assign(Component, { + meta: { + pluginId: config.pluginId, + title: config.title ?? '', + description: config.description ?? '', + id: generateExtensionId(config.pluginId, extensionPointId, config.title), + type: PluginExtensionTypes.component, + } satisfies PluginExtensionComponentMeta, + }); return ComponentWithMeta; } From ad7e06670364977a1c772acff0f23fff51571f44 Mon Sep 17 00:00:00 2001 From: Sarah Zinger Date: Tue, 16 Dec 2025 11:40:18 -0500 Subject: [PATCH 157/297] Feature Flags: Remove queryServiceFromExplore (#115168) * Remove feature flag: queryServiceFromExplore * Remove CoreApp --- packages/grafana-data/src/types/featureToggles.gen.ts | 4 ---- .../grafana-runtime/src/utils/DataSourceWithBackend.ts | 6 ------ pkg/services/featuremgmt/registry.go | 7 ------- pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.json | 3 ++- 5 files changed, 2 insertions(+), 19 deletions(-) diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 0eab199b022..04e4862f2ad 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -309,10 +309,6 @@ export interface FeatureToggles { */ queryServiceFromUI?: boolean; /** - * Routes explore requests to the new query service - */ - queryServiceFromExplore?: boolean; - /** * Runs CloudWatch metrics queries as separate batches */ cloudWatchBatchQueries?: boolean; diff --git a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts index 2e7153328e9..047e8c8fce9 100644 --- a/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts +++ b/packages/grafana-runtime/src/utils/DataSourceWithBackend.ts @@ -17,7 +17,6 @@ import { parseLiveChannelAddress, ScopedVars, AdHocVariableFilter, - CoreApp, } from '@grafana/data'; import { reportInteraction } from '../analytics/utils'; @@ -209,11 +208,6 @@ class DataSourceWithBackend< let url = '/api/ds/query?ds_type=' + this.type; - // Use the new query service for explore - if (config.featureToggles.queryServiceFromExplore && request.app === CoreApp.Explore) { - url = `/apis/query.grafana.app/v0alpha1/namespaces/${config.namespace}/query?ds_type=${this.type}`; - } - // Use the new query service if (config.featureToggles.queryServiceFromUI) { url = `/apis/query.grafana.app/v0alpha1/namespaces/${config.namespace}/query?ds_type=${this.type}`; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index d898492e918..2044c9c40f2 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -494,13 +494,6 @@ var ( Owner: grafanaDatasourcesCoreServicesSquad, FrontendOnly: true, // and can change at startup }, - { - Name: "queryServiceFromExplore", - Description: "Routes explore requests to the new query service", - Stage: FeatureStageExperimental, - Owner: grafanaDatasourcesCoreServicesSquad, - FrontendOnly: true, - }, { Name: "cloudWatchBatchQueries", Description: "Runs CloudWatch metrics queries as separate batches", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 51bb242f1d9..0f681344984 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -68,7 +68,6 @@ queryService,experimental,@grafana/grafana-datasources-core-services,false,true, queryServiceWithConnections,experimental,@grafana/grafana-datasources-core-services,false,true,false queryServiceRewrite,experimental,@grafana/grafana-datasources-core-services,false,true,false queryServiceFromUI,experimental,@grafana/grafana-datasources-core-services,false,false,true -queryServiceFromExplore,experimental,@grafana/grafana-datasources-core-services,false,false,true cloudWatchBatchQueries,preview,@grafana/aws-datasources,false,false,false cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-experience-squad,false,false,false alertmanagerRemoteSecondary,experimental,@grafana/alerting-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index b768dc2f761..31b501a8feb 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2911,7 +2911,8 @@ "metadata": { "name": "queryServiceFromExplore", "resourceVersion": "1764664939750", - "creationTimestamp": "2025-04-02T10:00:33Z" + "creationTimestamp": "2025-04-02T10:00:33Z", + "deletionTimestamp": "2025-12-10T20:33:21Z" }, "spec": { "description": "Routes explore requests to the new query service", From 482bb6a2fb4d9fd310faf8c12b1da5793df8638f Mon Sep 17 00:00:00 2001 From: mohammad-hamid Date: Tue, 16 Dec 2025 11:43:03 -0500 Subject: [PATCH 158/297] AuthZ: Redirect legacy resource permissions handler to k8s (part II) (#114356) * move restconfig to options * Add K8s API redirects for write operations * Revert restConfigProvider changes to receivers, service accounts, and teams * discard changing team permissions * lint * cleanup * trigger build * address feedback * improve test coverage * lint * trigger build * refactor --- .../accesscontrol/resourcepermissions/api.go | 72 +++++- .../resourcepermissions/api_adapter.go | 231 ++++++++++++++++++ .../resourcepermissions/api_adapter_test.go | 200 +++++++++++++++ 3 files changed, 499 insertions(+), 4 deletions(-) create mode 100644 pkg/services/accesscontrol/resourcepermissions/api_adapter_test.go diff --git a/pkg/services/accesscontrol/resourcepermissions/api.go b/pkg/services/accesscontrol/resourcepermissions/api.go index 981a99f5189..3c6d1da2038 100644 --- a/pkg/services/accesscontrol/resourcepermissions/api.go +++ b/pkg/services/accesscontrol/resourcepermissions/api.go @@ -33,6 +33,7 @@ type api struct { permissions []string features featuremgmt.FeatureToggles restConfigProvider apiserver.RestConfigProvider + logger log.Logger } func newApi(cfg *setting.Cfg, ac accesscontrol.AccessControl, router routing.RouteRegister, manager *Service, features featuremgmt.FeatureToggles, restConfigProvider apiserver.RestConfigProvider) *api { @@ -41,7 +42,23 @@ func newApi(cfg *setting.Cfg, ac accesscontrol.AccessControl, router routing.Rou for i := len(manager.permissions) - 1; i >= 0; i-- { permissions = append(permissions, manager.permissions[i]) } - return &api{cfg, ac, router, manager, permissions, features, restConfigProvider} + return &api{ + cfg: cfg, + ac: ac, + router: router, + service: manager, + permissions: permissions, + features: features, + restConfigProvider: restConfigProvider, + logger: log.New("resource-permissions-api"), + } +} + +// shouldUseK8sAPIs returns true if both feature flags for K8s API redirect are enabled +func (a *api) shouldUseK8sAPIs() bool { + //nolint:staticcheck // not yet migrated to OpenFeature + return a.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthZHandlerRedirect) && + a.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzResourcePermissionApis) } func (a *api) registerEndpoints() { @@ -189,11 +206,10 @@ func (a *api) getPermissions(c *contextmodel.ReqContext) response.Response { return response.JSON(http.StatusOK, k8sPermissions) } span.RecordError(err) - logger := log.New("resource-permissions-api") if errors.Is(err, ErrRestConfigNotAvailable) { - logger.Debug("k8s API not available for resource permissions, falling back to legacy", "error", err, "resourceID", resourceID, "resource", a.service.options.Resource) + a.logger.Debug("k8s API not available for resource permissions, falling back to legacy", "error", err, "resourceID", resourceID, "resource", a.service.options.Resource) } else { - logger.Warn("Failed to get resource permissions from k8s API, falling back to legacy", "error", err, "resourceID", resourceID, "resource", a.service.options.Resource) + a.logger.Warn("Failed to get resource permissions from k8s API, falling back to legacy", "error", err, "resourceID", resourceID, "resource", a.service.options.Resource) } } @@ -304,6 +320,19 @@ func (a *api) setUserPermission(c *contextmodel.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "bad request data", err) } + if a.shouldUseK8sAPIs() { + err := a.setUserPermissionToK8s(c.Req.Context(), c.Namespace, resourceID, userID, cmd.Permission) + if err == nil { + return permissionSetResponse(cmd) + } + span.RecordError(err) + if errors.Is(err, ErrRestConfigNotAvailable) { + a.logger.Debug("k8s API not available for resource permissions, falling back to legacy", "error", err, "resourceID", resourceID, "resource", a.service.options.Resource) + } else { + a.logger.Warn("Failed to set user permission in k8s API, falling back to legacy", "error", err, "resourceID", resourceID, "resource", a.service.options.Resource) + } + } + _, err = a.service.SetUserPermission(c.Req.Context(), c.GetOrgID(), accesscontrol.User{ID: userID}, resourceID, cmd.Permission) if err != nil { return response.Err(err) @@ -361,6 +390,19 @@ func (a *api) setTeamPermission(c *contextmodel.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "bad request data", err) } + if a.shouldUseK8sAPIs() { + err := a.setTeamPermissionToK8s(c.Req.Context(), c.Namespace, resourceID, teamID, cmd.Permission) + if err == nil { + return permissionSetResponse(cmd) + } + span.RecordError(err) + if errors.Is(err, ErrRestConfigNotAvailable) { + a.logger.Debug("k8s API not available for resource permissions, falling back to legacy", "error", err, "resourceID", resourceID, "resource", a.service.options.Resource) + } else { + a.logger.Warn("Failed to set team permission in k8s API, falling back to legacy", "error", err, "resourceID", resourceID, "resource", a.service.options.Resource) + } + } + _, err = a.service.SetTeamPermission(c.Req.Context(), c.GetOrgID(), teamID, resourceID, cmd.Permission) if err != nil { return response.Err(err) @@ -415,6 +457,17 @@ func (a *api) setBuiltinRolePermission(c *contextmodel.ReqContext) response.Resp return response.Error(http.StatusBadRequest, "bad request data", err) } + if a.shouldUseK8sAPIs() { + err := a.setBuiltInRolePermissionToK8s(c.Req.Context(), c.Namespace, resourceID, builtInRole, cmd.Permission) + if err == nil { + return permissionSetResponse(cmd) + } + span.RecordError(err) + if errors.Is(err, ErrRestConfigNotAvailable) { + a.logger.Debug("k8s API not available for resource permissions, falling back to legacy", "error", err, "resourceID", resourceID, "resource", a.service.options.Resource) + } + } + _, err := a.service.SetBuiltInRolePermission(c.Req.Context(), c.GetOrgID(), builtInRole, resourceID, cmd.Permission) if err != nil { return response.Err(err) @@ -463,6 +516,17 @@ func (a *api) setPermissions(c *contextmodel.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "Bad request data: "+err.Error(), err) } + if a.shouldUseK8sAPIs() { + err := a.setResourcePermissionsToK8s(c.Req.Context(), c.Namespace, resourceID, cmd.Permissions) + if err == nil { + return response.Success("Permissions updated") + } + span.RecordError(err) + if errors.Is(err, ErrRestConfigNotAvailable) { + a.logger.Debug("k8s API not available for resource permissions, falling back to legacy", "error", err, "resourceID", resourceID, "resource", a.service.options.Resource) + } + } + _, err := a.service.SetPermissions(ctx, c.GetOrgID(), resourceID, cmd.Permissions...) if err != nil { return response.Err(err) diff --git a/pkg/services/accesscontrol/resourcepermissions/api_adapter.go b/pkg/services/accesscontrol/resourcepermissions/api_adapter.go index 868ae1a32b5..578111368e5 100644 --- a/pkg/services/accesscontrol/resourcepermissions/api_adapter.go +++ b/pkg/services/accesscontrol/resourcepermissions/api_adapter.go @@ -13,9 +13,12 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/dynamic" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/user" ) @@ -162,3 +165,231 @@ func getMapKeys(m map[string][]string) []string { func (a *api) buildResourcePermissionName(resourceID string) string { return fmt.Sprintf("%s-%s-%s", a.getAPIGroup(), a.service.options.Resource, resourceID) } + +// Write operations + +func (a *api) setResourcePermissionsToK8s(ctx context.Context, namespace string, resourceID string, permissions []accesscontrol.SetResourcePermissionCommand) error { + dynamicClient, err := a.getDynamicClient(ctx) + if err != nil { + return err + } + + resourcePermName := a.buildResourcePermissionName(resourceID) + resourcePermResource := dynamicClient.Resource(iamv0.ResourcePermissionInfo.GroupVersionResource()).Namespace(namespace) + + _, existingResourceVersion, err := a.getExistingResourcePermission(ctx, resourcePermResource, resourcePermName) + if err != nil { + return err + } + + k8sPermissions := make([]iamv0.ResourcePermissionspecPermission, 0, len(permissions)) + for _, perm := range permissions { + if perm.Permission == "" { + continue + } + + kind := a.getPermissionKind(perm) + name, err := a.getPermissionName(ctx, perm) + if err != nil { + return fmt.Errorf("failed to get permission name: %w", err) + } + + k8sPermissions = append(k8sPermissions, iamv0.ResourcePermissionspecPermission{ + Kind: iamv0.ResourcePermissionSpecPermissionKind(kind), + Name: name, + Verb: cases.Lower(language.Und).String(perm.Permission), + }) + } + + if len(k8sPermissions) == 0 { + if existingResourceVersion != "" { + err = resourcePermResource.Delete(ctx, resourcePermName, metav1.DeleteOptions{}) + if err != nil && !k8serrors.IsNotFound(err) { + return fmt.Errorf("failed to delete resource permission in k8s: %w", err) + } + } + return nil + } + + resourcePerm := &iamv0.ResourcePermission{ + TypeMeta: metav1.TypeMeta{ + APIVersion: iamv0.ResourcePermissionInfo.GroupVersion().String(), + Kind: iamv0.ResourcePermissionInfo.TypeMeta().Kind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: resourcePermName, + Namespace: namespace, + ResourceVersion: existingResourceVersion, + }, + Spec: iamv0.ResourcePermissionSpec{ + Resource: iamv0.ResourcePermissionspecResource{ + ApiGroup: a.getAPIGroup(), + Resource: a.service.options.Resource, + Name: resourceID, + }, + Permissions: k8sPermissions, + }, + } + + return a.createOrUpdateResourcePermission(ctx, resourcePermResource, resourcePerm, existingResourceVersion != "") +} + +func (a *api) setUserPermissionToK8s(ctx context.Context, namespace string, resourceID string, userID int64, permission string) error { + userDetails, err := a.service.userService.GetByID(ctx, &user.GetUserByIDQuery{ID: userID}) + if err != nil { + return fmt.Errorf("failed to get user details: %w", err) + } + + return a.setSinglePermissionToK8s(ctx, namespace, resourceID, string(iamv0.ResourcePermissionSpecPermissionKindUser), userDetails.UID, permission) +} + +func (a *api) setTeamPermissionToK8s(ctx context.Context, namespace string, resourceID string, teamID int64, permission string) error { + teamDetails, err := a.service.teamService.GetTeamByID(ctx, &team.GetTeamByIDQuery{ID: teamID}) + if err != nil { + return fmt.Errorf("failed to get team details: %w", err) + } + + return a.setSinglePermissionToK8s(ctx, namespace, resourceID, string(iamv0.ResourcePermissionSpecPermissionKindTeam), teamDetails.UID, permission) +} + +func (a *api) setBuiltInRolePermissionToK8s(ctx context.Context, namespace string, resourceID string, builtInRole string, permission string) error { + return a.setSinglePermissionToK8s(ctx, namespace, resourceID, string(iamv0.ResourcePermissionSpecPermissionKindBasicRole), builtInRole, permission) +} + +func (a *api) setSinglePermissionToK8s(ctx context.Context, namespace string, resourceID string, kind string, name string, permission string) error { + dynamicClient, err := a.getDynamicClient(ctx) + if err != nil { + return err + } + + resourcePermName := a.buildResourcePermissionName(resourceID) + resourcePermResource := dynamicClient.Resource(iamv0.ResourcePermissionInfo.GroupVersionResource()).Namespace(namespace) + + existingResourcePerm, existingResourceVersion, err := a.getExistingResourcePermission(ctx, resourcePermResource, resourcePermName) + if err != nil { + return err + } + + newPermissions := make([]iamv0.ResourcePermissionspecPermission, 0) + for _, perm := range existingResourcePerm.Spec.Permissions { + if string(perm.Kind) == kind && perm.Name == name { + continue + } + newPermissions = append(newPermissions, perm) + } + + if permission != "" { + newPermissions = append(newPermissions, iamv0.ResourcePermissionspecPermission{ + Kind: iamv0.ResourcePermissionSpecPermissionKind(kind), + Name: name, + Verb: cases.Lower(language.Und).String(permission), + }) + } + + if len(newPermissions) == 0 { + if existingResourceVersion != "" { + err = resourcePermResource.Delete(ctx, resourcePermName, metav1.DeleteOptions{}) + if err != nil && !k8serrors.IsNotFound(err) { + return fmt.Errorf("failed to delete resource permission in k8s: %w", err) + } + } + return nil + } + + resourcePerm := &iamv0.ResourcePermission{ + TypeMeta: metav1.TypeMeta{ + APIVersion: iamv0.ResourcePermissionInfo.GroupVersion().String(), + Kind: iamv0.ResourcePermissionInfo.TypeMeta().Kind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: resourcePermName, + Namespace: namespace, + ResourceVersion: existingResourceVersion, + }, + Spec: iamv0.ResourcePermissionSpec{ + Resource: iamv0.ResourcePermissionspecResource{ + ApiGroup: a.getAPIGroup(), + Resource: a.service.options.Resource, + Name: resourceID, + }, + Permissions: newPermissions, + }, + } + + return a.createOrUpdateResourcePermission(ctx, resourcePermResource, resourcePerm, existingResourceVersion != "") +} + +func (a *api) getPermissionKind(perm accesscontrol.SetResourcePermissionCommand) string { + if perm.UserID != 0 { + return string(iamv0.ResourcePermissionSpecPermissionKindUser) + } + if perm.TeamID != 0 { + return string(iamv0.ResourcePermissionSpecPermissionKindTeam) + } + if perm.BuiltinRole != "" { + return string(iamv0.ResourcePermissionSpecPermissionKindBasicRole) + } + return "" +} + +func (a *api) getExistingResourcePermission(ctx context.Context, resourcePermResource dynamic.ResourceInterface, resourcePermName string) (*iamv0.ResourcePermission, string, error) { + unstructuredObj, err := resourcePermResource.Get(ctx, resourcePermName, metav1.GetOptions{}) + if err != nil { + if k8serrors.IsNotFound(err) { + return &iamv0.ResourcePermission{}, "", nil + } + return nil, "", fmt.Errorf("failed to get existing resource permission: %w", err) + } + + var resourcePerm iamv0.ResourcePermission + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredObj.Object, &resourcePerm); err != nil { + return nil, "", fmt.Errorf("failed to convert existing resource permission: %w", err) + } + + return &resourcePerm, unstructuredObj.GetResourceVersion(), nil +} + +func (a *api) createOrUpdateResourcePermission(ctx context.Context, resourcePermResource dynamic.ResourceInterface, resourcePerm *iamv0.ResourcePermission, isUpdate bool) error { + unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(resourcePerm) + if err != nil { + return fmt.Errorf("failed to convert resource permission to unstructured: %w", err) + } + unstructuredPerm := &unstructured.Unstructured{Object: unstructuredObj} + + if isUpdate { + _, err = resourcePermResource.Update(ctx, unstructuredPerm, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("failed to update resource permission in k8s: %w", err) + } + } else { + _, err = resourcePermResource.Create(ctx, unstructuredPerm, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create resource permission in k8s: %w", err) + } + } + + return nil +} + +func (a *api) getPermissionName(ctx context.Context, perm accesscontrol.SetResourcePermissionCommand) (string, error) { + if perm.UserID != 0 { + userDetails, err := a.service.userService.GetByID(ctx, &user.GetUserByIDQuery{ID: perm.UserID}) + if err != nil { + return "", fmt.Errorf("failed to get user details for user ID %d: %w", perm.UserID, err) + } + return userDetails.UID, nil + } + if perm.TeamID != 0 { + teamDetails, err := a.service.teamService.GetTeamByID(ctx, &team.GetTeamByIDQuery{ + ID: perm.TeamID, + }) + if err != nil { + return "", fmt.Errorf("failed to get team details for team ID %d: %w", perm.TeamID, err) + } + return teamDetails.UID, nil + } + if perm.BuiltinRole != "" { + return perm.BuiltinRole, nil + } + return "", fmt.Errorf("no valid permission subject found") +} diff --git a/pkg/services/accesscontrol/resourcepermissions/api_adapter_test.go b/pkg/services/accesscontrol/resourcepermissions/api_adapter_test.go new file mode 100644 index 00000000000..ca0ca214c9a --- /dev/null +++ b/pkg/services/accesscontrol/resourcepermissions/api_adapter_test.go @@ -0,0 +1,200 @@ +package resourcepermissions + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/services/accesscontrol" +) + +// TestGetPermissionKind tests the permission kind mapping logic +func TestGetPermissionKind(t *testing.T) { + api := &api{ + service: &Service{ + options: Options{ + Resource: "dashboards", + ResourceAttribute: "uid", + }, + }, + } + + tests := []struct { + name string + perm accesscontrol.SetResourcePermissionCommand + expected string + }{ + { + name: "user permission", + perm: accesscontrol.SetResourcePermissionCommand{UserID: 123}, + expected: string(iamv0.ResourcePermissionSpecPermissionKindUser), + }, + { + name: "team permission", + perm: accesscontrol.SetResourcePermissionCommand{TeamID: 456}, + expected: string(iamv0.ResourcePermissionSpecPermissionKindTeam), + }, + { + name: "builtin role permission", + perm: accesscontrol.SetResourcePermissionCommand{BuiltinRole: "Editor"}, + expected: string(iamv0.ResourcePermissionSpecPermissionKindBasicRole), + }, + { + name: "empty permission returns empty kind", + perm: accesscontrol.SetResourcePermissionCommand{}, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kind := api.getPermissionKind(tt.perm) + assert.Equal(t, tt.expected, kind) + }) + } +} + +// TestGetDynamicClient_RestConfigNotAvailable tests error handling when rest config is not available +func TestGetDynamicClient_RestConfigNotAvailable(t *testing.T) { + ctx := context.Background() + + api := &api{ + service: &Service{ + options: Options{ + Resource: "dashboards", + }, + }, + restConfigProvider: nil, + } + + client, err := api.getDynamicClient(ctx) + + assert.Error(t, err) + assert.Nil(t, client) + assert.Equal(t, ErrRestConfigNotAvailable, err) +} + +// TestBuildResourcePermissionName tests resource permission name building +func TestBuildResourcePermissionName(t *testing.T) { + tests := []struct { + name string + apiGroup string + resource string + resourceID string + expectedName string + }{ + { + name: "with custom API group", + apiGroup: "dashboard.grafana.app", + resource: "dashboards", + resourceID: "dashboard-uid-123", + expectedName: "dashboard.grafana.app-dashboards-dashboard-uid-123", + }, + { + name: "with default API group", + apiGroup: "", + resource: "folders", + resourceID: "folder-uid-456", + expectedName: "folders.grafana.app-folders-folder-uid-456", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + api := &api{ + service: &Service{ + options: Options{ + Resource: tt.resource, + APIGroup: tt.apiGroup, + }, + }, + } + + name := api.buildResourcePermissionName(tt.resourceID) + assert.Equal(t, tt.expectedName, name) + }) + } +} + +// TestGetAPIGroup tests API group resolution +func TestGetAPIGroup(t *testing.T) { + t.Run("returns custom API group when set", func(t *testing.T) { + api := &api{ + service: &Service{ + options: Options{ + Resource: "dashboards", + APIGroup: "custom.grafana.app", + }, + }, + } + + group := api.getAPIGroup() + assert.Equal(t, "custom.grafana.app", group) + }) + + t.Run("returns default API group when not set", func(t *testing.T) { + api := &api{ + service: &Service{ + options: Options{ + Resource: "dashboards", + APIGroup: "", + }, + }, + } + + group := api.getAPIGroup() + assert.Equal(t, "dashboards.grafana.app", group) + }) + + t.Run("default group for folders", func(t *testing.T) { + api := &api{ + service: &Service{ + options: Options{ + Resource: "folders", + APIGroup: "", + }, + }, + } + + group := api.getAPIGroup() + assert.Equal(t, "folders.grafana.app", group) + }) +} + +// TestResourcePermissionKindConstants verifies the kind constants match expected values +func TestResourcePermissionKindConstants(t *testing.T) { + tests := []struct { + name string + kind iamv0.ResourcePermissionSpecPermissionKind + expected string + }{ + { + name: "User kind", + kind: iamv0.ResourcePermissionSpecPermissionKindUser, + expected: "User", + }, + { + name: "Team kind", + kind: iamv0.ResourcePermissionSpecPermissionKindTeam, + expected: "Team", + }, + { + name: "ServiceAccount kind", + kind: iamv0.ResourcePermissionSpecPermissionKindServiceAccount, + expected: "ServiceAccount", + }, + { + name: "BasicRole kind", + kind: iamv0.ResourcePermissionSpecPermissionKindBasicRole, + expected: "BasicRole", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, string(tt.kind)) + }) + } +} From 6dd711b6f2d78684bb5e87da61f879a2861c6ef0 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:52:08 +0100 Subject: [PATCH 159/297] Scopes: Fix selector crashing on unavailable node (#115417) * Fix crash * Add regression tests * Remove component mocking --- .../scopes/selector/ScopesTree.test.tsx | 248 ++++++++++++++++++ .../features/scopes/selector/ScopesTree.tsx | 29 +- .../scopes/selector/ScopesTreeHeadline.tsx | 2 +- .../selector/ScopesTreeItemList.test.tsx | 136 ++++++++++ .../scopes/selector/ScopesTreeItemList.tsx | 9 +- 5 files changed, 407 insertions(+), 17 deletions(-) create mode 100644 public/app/features/scopes/selector/ScopesTree.test.tsx create mode 100644 public/app/features/scopes/selector/ScopesTreeItemList.test.tsx diff --git a/public/app/features/scopes/selector/ScopesTree.test.tsx b/public/app/features/scopes/selector/ScopesTree.test.tsx new file mode 100644 index 00000000000..2ac4b647a5c --- /dev/null +++ b/public/app/features/scopes/selector/ScopesTree.test.tsx @@ -0,0 +1,248 @@ +import { render, screen } from '@testing-library/react'; + +import { ScopeNode } from '@grafana/data'; + +import { ScopesTree } from './ScopesTree'; +import { NodesMap, SelectedScope, TreeNode } from './types'; + +// Mock the ScopesContextProvider hook since it requires a full context setup +jest.mock('../ScopesContextProvider', () => ({ + useScopesServices: () => ({ + scopesSelectorService: { + closeAndApply: jest.fn(), + }, + }), +})); + +describe('ScopesTree', () => { + const mockFilterNode = jest.fn(); + const mockSelectScope = jest.fn(); + const mockDeselectScope = jest.fn(); + const mockToggleExpandedNode = jest.fn(); + + const createMockScopeNode = (name: string, parentName?: string): ScopeNode => ({ + metadata: { name }, + spec: { + title: `Title ${name}`, + nodeType: 'leaf', + linkType: 'scope', + linkId: `scope-${name}`, + parentName: parentName ?? '', + }, + }); + + const defaultScopeNodes: NodesMap = { + 'parent-container': { + metadata: { name: 'parent-container' }, + spec: { + title: 'Parent Container', + nodeType: 'container', + parentName: '', + }, + }, + 'child-1': createMockScopeNode('child-1', 'parent-container'), + 'child-2': createMockScopeNode('child-2', 'parent-container'), + }; + + const defaultTree: TreeNode = { + scopeNodeId: 'parent-container', + expanded: true, + query: '', + children: { + 'child-1': { scopeNodeId: 'child-1', expanded: false, query: '' }, + 'child-2': { scopeNodeId: 'child-2', expanded: false, query: '' }, + }, + childrenLoaded: true, + }; + + const defaultProps = { + tree: defaultTree, + loadingNodeName: undefined, + selectedScopes: [] as SelectedScope[], + scopeNodes: defaultScopeNodes, + filterNode: mockFilterNode, + selectScope: mockSelectScope, + deselectScope: mockDeselectScope, + toggleExpandedNode: mockToggleExpandedNode, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('selectedNodesToShow logic', () => { + it('should not show selectedNodesToShow when no scopes are selected', () => { + render(); + + // Both child-1 and child-2 should be visible in the regular children list + expect(screen.getByText('Title child-1')).toBeInTheDocument(); + expect(screen.getByText('Title child-2')).toBeInTheDocument(); + }); + + it('should only consider first selected scope for selectedNodesToShow', () => { + const selectedScopes: SelectedScope[] = [ + { scopeId: 'scope-1', scopeNodeId: 'child-1' }, + { scopeId: 'scope-2', scopeNodeId: 'child-2' }, + ]; + + // Use a tree where child-1 is NOT in the children (to trigger selectedNodesToShow) + const tree: TreeNode = { + scopeNodeId: 'parent-container', + expanded: true, + query: '', + children: { + // child-1 is NOT here, so it should appear in selectedNodesToShow + 'child-2': { scopeNodeId: 'child-2', expanded: false, query: '' }, + }, + childrenLoaded: true, + }; + + render(); + + // child-1 should be shown (from selectedNodesToShow - only first scope is considered) + expect(screen.getByText('Title child-1')).toBeInTheDocument(); + // child-2 should also be shown (from regular children) + expect(screen.getByText('Title child-2')).toBeInTheDocument(); + }); + + it('should not show selectedNodesToShow when first scope has no scopeNodeId', () => { + const selectedScopes: SelectedScope[] = [ + { scopeId: 'scope-1', scopeNodeId: undefined }, // No scopeNodeId + { scopeId: 'scope-2', scopeNodeId: 'child-2' }, + ]; + + // Tree with no children to make it obvious if selectedNodesToShow is populated + const tree: TreeNode = { + scopeNodeId: 'parent-container', + expanded: true, + query: '', + children: {}, + childrenLoaded: true, + }; + + render(); + + // child-2 should NOT appear because only first scope is considered and it has no scopeNodeId + expect(screen.queryByText('Title child-2')).not.toBeInTheDocument(); + }); + + it('should not show selectedNodesToShow when first scope node is not in scopeNodes cache', () => { + const selectedScopes: SelectedScope[] = [ + { scopeId: 'scope-1', scopeNodeId: 'missing-node' }, // Node not in scopeNodes + { scopeId: 'scope-2', scopeNodeId: 'child-2' }, + ]; + + // Tree with no children + const tree: TreeNode = { + scopeNodeId: 'parent-container', + expanded: true, + query: '', + children: {}, + childrenLoaded: true, + }; + + render(); + + // Neither should appear since first scope's node is missing from cache + expect(screen.queryByText('Title missing-node')).not.toBeInTheDocument(); + expect(screen.queryByText('Title child-2')).not.toBeInTheDocument(); + }); + + it('should not show selectedNodesToShow when tree scopeNodeId does not match first scope parent', () => { + const selectedScopes: SelectedScope[] = [ + { scopeId: 'scope-1', scopeNodeId: 'child-1' }, // child-1's parent is 'parent-container' + ]; + + // Tree with different scopeNodeId + const tree: TreeNode = { + scopeNodeId: 'different-container', // Different from child-1's parent + expanded: true, + query: '', + children: {}, + childrenLoaded: true, + }; + + const scopeNodes: NodesMap = { + ...defaultScopeNodes, + 'different-container': { + metadata: { name: 'different-container' }, + spec: { title: 'Different', nodeType: 'container', parentName: '' }, + }, + }; + + render(); + + // child-1 should NOT appear since tree's scopeNodeId doesn't match child-1's parent + expect(screen.queryByText('Title child-1')).not.toBeInTheDocument(); + }); + + it('should not duplicate scope in selectedNodesToShow if already in children', () => { + const selectedScopes: SelectedScope[] = [{ scopeId: 'scope-1', scopeNodeId: 'child-1' }]; + + // Tree already has child-1 in children + const tree: TreeNode = { + scopeNodeId: 'parent-container', + expanded: true, + query: '', + children: { + 'child-1': { scopeNodeId: 'child-1', expanded: false, query: '' }, + 'child-2': { scopeNodeId: 'child-2', expanded: false, query: '' }, + }, + childrenLoaded: true, + }; + + render(); + + // child-1 should appear exactly once (in regular children, not duplicated) + const child1Elements = screen.getAllByText('Title child-1'); + expect(child1Elements).toHaveLength(1); + }); + }); + + describe('graceful handling of missing data', () => { + it('should not crash when scopeNodes is empty', () => { + const tree: TreeNode = { + scopeNodeId: '', + expanded: true, + query: '', + children: {}, + childrenLoaded: true, + }; + + render(); + + // Should render without crashing - search input should be present + expect(screen.getByRole('combobox')).toBeInTheDocument(); + }); + + it('should handle tree with children referencing missing nodes', () => { + const tree: TreeNode = { + scopeNodeId: 'parent-container', + expanded: true, + query: '', + children: { + 'existing-node': { scopeNodeId: 'existing-node', expanded: false, query: '' }, + 'missing-node': { scopeNodeId: 'missing-node', expanded: false, query: '' }, + }, + childrenLoaded: true, + }; + + const scopeNodes: NodesMap = { + 'parent-container': { + metadata: { name: 'parent-container' }, + spec: { title: 'Parent', nodeType: 'container', parentName: '' }, + }, + 'existing-node': createMockScopeNode('existing-node', 'parent-container'), + // 'missing-node' intentionally not included + }; + + // Should render without crashing + render(); + + // Existing node should be rendered + expect(screen.getByText('Title existing-node')).toBeInTheDocument(); + // Missing node should be gracefully skipped + expect(screen.queryByText('Title missing-node')).not.toBeInTheDocument(); + }); + }); +}); diff --git a/public/app/features/scopes/selector/ScopesTree.tsx b/public/app/features/scopes/selector/ScopesTree.tsx index dd2fd61184c..8178acd3368 100644 --- a/public/app/features/scopes/selector/ScopesTree.tsx +++ b/public/app/features/scopes/selector/ScopesTree.tsx @@ -55,21 +55,22 @@ export function ScopesTree({ const anyChildExpanded = childrenArray.some(({ expanded }) => expanded); // Nodes that are already selected (not applied) are always shown if we are in their category, even if they are - // filtered out by query filter + // filtered out by query filter. Only consider the first selected scope for this display logic. let selectedNodesToShow: TreeNode[] = []; - if (selectedScopes.length > 0 && selectedScopes[0].scopeNodeId) { - if (tree.scopeNodeId === scopeNodes[selectedScopes[0].scopeNodeId]?.spec.parentName) { - selectedNodesToShow = selectedScopes - // We filter out those which are still shown in the normal list of results - .filter((s) => !childrenArray.map((c) => c.scopeNodeId).includes(s.scopeNodeId!)) - .map((s) => ({ - // Because we had to check the parent with the use of scopeNodeId we know we have it. (we may not have it - // if the selected scopes are from url persistence, in which case we don't show them) - scopeNodeId: s.scopeNodeId!, - query: '', - expanded: false, - })); - } + const firstSelectedScope = selectedScopes[0]; + if ( + firstSelectedScope?.scopeNodeId && + scopeNodes[firstSelectedScope.scopeNodeId] && + tree.scopeNodeId === scopeNodes[firstSelectedScope.scopeNodeId]?.spec.parentName && + !childrenArray.map((c) => c.scopeNodeId).includes(firstSelectedScope.scopeNodeId) + ) { + selectedNodesToShow = [ + { + scopeNodeId: firstSelectedScope.scopeNodeId, + query: '', + expanded: false, + }, + ]; } const { highlightedId, ariaActiveDescendant, enableHighlighting, disableHighlighting } = useScopesHighlighting({ diff --git a/public/app/features/scopes/selector/ScopesTreeHeadline.tsx b/public/app/features/scopes/selector/ScopesTreeHeadline.tsx index 755fadf64b5..44ed80ce1bf 100644 --- a/public/app/features/scopes/selector/ScopesTreeHeadline.tsx +++ b/public/app/features/scopes/selector/ScopesTreeHeadline.tsx @@ -18,7 +18,7 @@ export function ScopesTreeHeadline({ anyChildExpanded, query, resultsNodes, scop if ( anyChildExpanded || - (resultsNodes.some((n) => scopeNodes[n.scopeNodeId].spec.nodeType === 'container') && !query) + (resultsNodes.some((n) => scopeNodes[n.scopeNodeId]?.spec.nodeType === 'container') && !query) ) { return null; } diff --git a/public/app/features/scopes/selector/ScopesTreeItemList.test.tsx b/public/app/features/scopes/selector/ScopesTreeItemList.test.tsx new file mode 100644 index 00000000000..a93bb456a83 --- /dev/null +++ b/public/app/features/scopes/selector/ScopesTreeItemList.test.tsx @@ -0,0 +1,136 @@ +import { render, screen } from '@testing-library/react'; + +import { ScopeNode } from '@grafana/data'; + +import { ScopesTreeItemList } from './ScopesTreeItemList'; +import { NodesMap, SelectedScope, TreeNode } from './types'; + +// Mock the ScopesContextProvider hook since it requires a full context setup +jest.mock('../ScopesContextProvider', () => ({ + useScopesServices: () => ({ + scopesSelectorService: { + closeAndApply: jest.fn(), + }, + }), +})); + +describe('ScopesTreeItemList', () => { + const mockFilterNode = jest.fn(); + const mockSelectScope = jest.fn(); + const mockDeselectScope = jest.fn(); + const mockToggleExpandedNode = jest.fn(); + + const defaultProps = { + anyChildExpanded: false, + lastExpandedNode: false, + loadingNodeName: undefined, + maxHeight: '100%', + selectedScopes: [] as SelectedScope[], + filterNode: mockFilterNode, + selectScope: mockSelectScope, + deselectScope: mockDeselectScope, + highlightedId: undefined, + id: 'test-tree', + toggleExpandedNode: mockToggleExpandedNode, + }; + + const createMockScopeNode = (name: string, parentName = 'parent'): ScopeNode => ({ + metadata: { name }, + spec: { + title: `Title ${name}`, + nodeType: 'leaf', + linkType: 'scope', + linkId: `scope-${name}`, + parentName, + }, + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render nothing when items array is empty', () => { + const { container } = render(); + + expect(container.firstChild).toBeNull(); + }); + + it('should render tree items when nodes are available', () => { + const items: TreeNode[] = [ + { scopeNodeId: 'node-1', expanded: false, query: '' }, + { scopeNodeId: 'node-2', expanded: false, query: '' }, + ]; + + const scopeNodes: NodesMap = { + 'node-1': createMockScopeNode('node-1'), + 'node-2': createMockScopeNode('node-2'), + parent: { + metadata: { name: 'parent' }, + spec: { title: 'Parent', nodeType: 'container', parentName: '' }, + }, + }; + + render(); + + expect(screen.getByText('Title node-1')).toBeInTheDocument(); + expect(screen.getByText('Title node-2')).toBeInTheDocument(); + }); + + it('should skip rendering items when node data is not available in scopeNodes', () => { + const items: TreeNode[] = [ + { scopeNodeId: 'node-1', expanded: false, query: '' }, + { scopeNodeId: 'missing-node', expanded: false, query: '' }, // This node doesn't exist in scopeNodes + { scopeNodeId: 'node-2', expanded: false, query: '' }, + ]; + + const scopeNodes: NodesMap = { + 'node-1': createMockScopeNode('node-1'), + 'node-2': createMockScopeNode('node-2'), + parent: { + metadata: { name: 'parent' }, + spec: { title: 'Parent', nodeType: 'container', parentName: '' }, + }, + // 'missing-node' is intentionally not included + }; + + render(); + + // Should render the available nodes + expect(screen.getByText('Title node-1')).toBeInTheDocument(); + expect(screen.getByText('Title node-2')).toBeInTheDocument(); + + // Should NOT crash and should skip the missing node + expect(screen.queryByText('Title missing-node')).not.toBeInTheDocument(); + }); + + it('should handle all items having missing node data gracefully', () => { + const items: TreeNode[] = [ + { scopeNodeId: 'missing-1', expanded: false, query: '' }, + { scopeNodeId: 'missing-2', expanded: false, query: '' }, + ]; + + const scopeNodes: NodesMap = {}; + + // Should not crash + const { container } = render(); + + // Container should have the tree div but no visible items rendered inside + expect(container.querySelector('[role="tree"]')).toBeInTheDocument(); + expect(screen.queryByRole('treeitem')).not.toBeInTheDocument(); + }); + + it('should handle empty string scopeNodeId gracefully', () => { + const items: TreeNode[] = [ + { scopeNodeId: '', expanded: false, query: '' }, // Empty string scopeNodeId + ]; + + const scopeNodes: NodesMap = {}; + + // Should not crash + const { container } = render(); + + // Should have tree container but no items + expect(container.querySelector('[role="tree"]')).toBeInTheDocument(); + expect(screen.queryByRole('treeitem')).not.toBeInTheDocument(); + }); +}); diff --git a/public/app/features/scopes/selector/ScopesTreeItemList.tsx b/public/app/features/scopes/selector/ScopesTreeItemList.tsx index 33d3a4e2b53..e28a8cda214 100644 --- a/public/app/features/scopes/selector/ScopesTreeItemList.tsx +++ b/public/app/features/scopes/selector/ScopesTreeItemList.tsx @@ -47,15 +47,20 @@ export function ScopesTreeItemList({ const children = (
{items.map((childNode) => { + const node = scopeNodes[childNode.scopeNodeId]; + // Skip rendering if node data isn't available + if (!node) { + return null; + } const selected = - isNodeSelectable(scopeNodes[childNode.scopeNodeId]) && + isNodeSelectable(node) && selectedScopes.some((s) => { if (s.scopeNodeId) { // If we have scopeNodeId we only match based on that so even if the actual scope is the same we don't // mark different scopeNode as selected. return s.scopeNodeId === childNode.scopeNodeId; } else { - return s.scopeId === scopeNodes[childNode.scopeNodeId]?.spec.linkId; + return s.scopeId === node.spec.linkId; } }); return ( From f64daba9dd787f2435cf434027dd4742f709d0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 16 Dec 2025 17:54:49 +0100 Subject: [PATCH 160/297] Dropdown: Don't pass getFloatingProps to content function (#115415) * Dropdown: Revert accidential cahnge * Update * update --- packages/grafana-ui/src/components/Dropdown/Dropdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx b/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx index 6332b5ceefa..ed1053f09a4 100644 --- a/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx +++ b/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx @@ -111,7 +111,7 @@ export const Dropdown = React.memo(({ children, overlay, placement, offset, root timeout={{ appear: animationDuration, exit: 0, enter: 0 }} classNames={animationStyles} > -
{renderOrCallToRender(overlay, { ...getFloatingProps() })}
+
{renderOrCallToRender(overlay, {})}
From 81710ca1f5e1fd870c19c65ec60aab5d237c811c Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Tue, 16 Dec 2025 18:09:32 +0100 Subject: [PATCH 161/297] Alerting: Fix saving result_fingerprint in async alert state persister (#115005) --- pkg/services/ngalert/models/testing.go | 6 ++++ .../ngalert/store/instance_database.go | 7 ++-- .../ngalert/store/instance_database_test.go | 36 +++++++++++-------- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/pkg/services/ngalert/models/testing.go b/pkg/services/ngalert/models/testing.go index 886f13d029a..9542c35fce8 100644 --- a/pkg/services/ngalert/models/testing.go +++ b/pkg/services/ngalert/models/testing.go @@ -973,6 +973,12 @@ func (a AlertInstanceMutators) WithAnnotations(annotations InstanceAnnotations) } } +func (a AlertInstanceMutators) WithResultFingerprint(fp string) AlertInstanceMutator { + return func(i *AlertInstance) { + i.ResultFingerprint = fp + } +} + type Mutator[T any] func(*T) // CopyNotificationSettings creates a deep copy of NotificationSettings. diff --git a/pkg/services/ngalert/store/instance_database.go b/pkg/services/ngalert/store/instance_database.go index a9d57d1ab8f..cc385ef4c63 100644 --- a/pkg/services/ngalert/store/instance_database.go +++ b/pkg/services/ngalert/store/instance_database.go @@ -364,10 +364,10 @@ func (st InstanceDBStore) insertInstancesBatch(sess *sqlstore.DBSession, batch [ query := strings.Builder{} placeholders := make([]string, 0, len(batch)) - args := make([]any, 0, len(batch)*13) + args := make([]any, 0, len(batch)*14) query.WriteString("INSERT INTO alert_instance ") - query.WriteString("(rule_org_id, rule_uid, labels, labels_hash, current_state, current_reason, current_state_since, current_state_end, last_eval_time, fired_at, resolved_at, last_sent_at, annotations) VALUES ") + query.WriteString("(rule_org_id, rule_uid, labels, labels_hash, current_state, current_reason, current_state_since, current_state_end, last_eval_time, fired_at, resolved_at, last_sent_at, result_fingerprint, annotations) VALUES ") for _, instance := range batch { if err := models.ValidateAlertInstance(instance); err != nil { @@ -387,7 +387,7 @@ func (st InstanceDBStore) insertInstancesBatch(sess *sqlstore.DBSession, batch [ continue } - placeholders = append(placeholders, "(?,?,?,?,?,?,?,?,?,?,?,?,?)") + placeholders = append(placeholders, "(?,?,?,?,?,?,?,?,?,?,?,?,?,?)") args = append(args, instance.RuleOrgID, instance.RuleUID, @@ -401,6 +401,7 @@ func (st InstanceDBStore) insertInstancesBatch(sess *sqlstore.DBSession, batch [ nullableTimeToUnix(instance.FiredAt), nullableTimeToUnix(instance.ResolvedAt), nullableTimeToUnix(instance.LastSentAt), + instance.ResultFingerprint, annotationsJSON, ) } diff --git a/pkg/services/ngalert/store/instance_database_test.go b/pkg/services/ngalert/store/instance_database_test.go index 3cf264d4a36..4f833d0fd17 100644 --- a/pkg/services/ngalert/store/instance_database_test.go +++ b/pkg/services/ngalert/store/instance_database_test.go @@ -8,6 +8,8 @@ import ( "time" "github.com/golang/snappy" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/grafana/grafana/pkg/util/testutil" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" @@ -391,27 +393,33 @@ func TestIntegrationFullSync(t *testing.T) { t.Run("Should save all instances when batch size is bigger than 1", func(t *testing.T) { batchSize = 2 - newRuleUID := "y" - err := ng.InstanceStore.FullSync(ctx, append(instances, *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID(newRuleUID))), batchSize, nil) + testInstances := []models.AlertInstance{ + *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID("batch1"), models.InstanceMuts.WithResultFingerprint("fp0")), + *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID("batch2"), models.InstanceMuts.WithResultFingerprint("fp1")), + *models.AlertInstanceGen(models.InstanceMuts.WithOrgID(orgID), models.InstanceMuts.WithRuleUID("batch3"), models.InstanceMuts.WithResultFingerprint("fp2")), + } + + err := ng.InstanceStore.FullSync(ctx, testInstances, batchSize, nil) require.NoError(t, err) res, err := ng.InstanceStore.ListAlertInstances(ctx, &models.ListAlertInstancesQuery{ RuleOrgID: orgID, }) require.NoError(t, err) - require.Len(t, res, len(instances)+1) - for _, ruleUID := range append(ruleUIDs, newRuleUID) { - found := false - for _, instance := range res { - if instance.RuleUID == ruleUID { - found = true - continue - } - } - if !found { - t.Errorf("Instance with RuleUID '%s' not found", ruleUID) - } + + savedInstances := make([]models.AlertInstance, len(res)) + for i, r := range res { + savedInstances[i] = *r } + + opts := []cmp.Option{ + cmpopts.EquateApproxTime(time.Second), // we don't get the same precision back from the DB + cmpopts.EquateEmpty(), + cmpopts.SortSlices(func(a, b models.AlertInstance) bool { + return a.RuleUID < b.RuleUID + }), + } + require.Empty(t, cmp.Diff(testInstances, savedInstances, opts...)) }) t.Run("Should not fail when the instances are empty", func(t *testing.T) { From f8069aebcfe5f34657c09d367db5e131d38d7541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Jim=C3=A9nez=20S=C3=A1nchez?= Date: Tue, 16 Dec 2025 18:26:32 +0100 Subject: [PATCH 162/297] Provisioning: delegate authorization to access checker in dualwriter (#115407) * refactor: delegate authorization to access checker in dualwriter - Remove role-based authorization checks (editor/admin role checks) - Delegate all authorization to access checker which checks resource-level permissions - Update authorizeCreateFolder to use access checker instead of role-based checks - Add comprehensive authorization tests for viewer, editor, and admin roles - Tests cover GET, POST, PUT, DELETE operations and folder creation This change ensures that authorization is consistently handled through the access checker, which checks resource-level permissions rather than just organization roles. * fix: format files_test.go * fix: check error return value of resp.Body.Close() * fix: grant permissions to all dashboards for editor role in authorization test Use SetPermissions with wildcard to grant permissions to Editor user for all dashboards, not just the initial one. This ensures that dashboards created during tests (like in DELETE operations) have the necessary permissions for the editor role. --- .../apis/provisioning/resources/dualwriter.go | 44 +- pkg/tests/apis/provisioning/files_test.go | 390 ++++++++++++++++++ 2 files changed, 416 insertions(+), 18 deletions(-) diff --git a/pkg/registry/apis/provisioning/resources/dualwriter.go b/pkg/registry/apis/provisioning/resources/dualwriter.go index 8ffcee696e8..7c9005a8dd5 100644 --- a/pkg/registry/apis/provisioning/resources/dualwriter.go +++ b/pkg/registry/apis/provisioning/resources/dualwriter.go @@ -503,35 +503,43 @@ func (r *DualReadWriter) authorize(ctx context.Context, parsed *ParsedResource, }, parsed.Meta.GetFolder()) if err != nil || !rsp.Allowed { return apierrors.NewForbidden(parsed.GVR.GroupResource(), parsed.Obj.GetName(), - fmt.Errorf("no access to read the embedded file")) + fmt.Errorf("no access to perform %s on the resource", verb)) } - idType, _, err := authlib.ParseTypeID(id.GetID()) - if err != nil { - return apierrors.NewForbidden(parsed.GVR.GroupResource(), parsed.Obj.GetName(), fmt.Errorf("could not determine identity type to check access")) - } - // only apply role based access if identity is not of type access policy - if idType == authlib.TypeAccessPolicy || id.GetOrgRole().Includes(identity.RoleEditor) { - return nil - } - - return apierrors.NewForbidden(parsed.GVR.GroupResource(), parsed.Obj.GetName(), - fmt.Errorf("must be admin or editor to access files from provisioning")) + return nil } -func (r *DualReadWriter) authorizeCreateFolder(ctx context.Context, _ string) error { +func (r *DualReadWriter) authorizeCreateFolder(ctx context.Context, path string) error { id, err := identity.GetRequester(ctx) if err != nil { return apierrors.NewUnauthorized(err.Error()) } - // Simple role based access for now - if id.GetOrgRole().Includes(identity.RoleEditor) { - return nil + // Determine parent folder from path + parentFolder := "" + if path != "" { + parentPath := safepath.Dir(path) + if parentPath != "" { + parentFolder = ParseFolder(parentPath, r.repo.Config().Name).ID + } else { + parentFolder = RootFolder(r.repo.Config()) + } } - return apierrors.NewForbidden(FolderResource.GroupResource(), "", - fmt.Errorf("must be admin or editor to access folders with provisioning")) + // For folder create operations, use empty name to check parent folder permissions + rsp, err := r.access.Check(ctx, id, authlib.CheckRequest{ + Group: FolderResource.Group, + Resource: FolderResource.Resource, + Namespace: id.GetNamespace(), + Name: "", // Empty name for create operations + Verb: utils.VerbCreate, + }, parentFolder) + if err != nil || !rsp.Allowed { + return apierrors.NewForbidden(FolderResource.GroupResource(), path, + fmt.Errorf("no access to create folder in parent folder '%s'", parentFolder)) + } + + return nil } func (r *DualReadWriter) deleteFolder(ctx context.Context, opts DualWriteOptions) (*ParsedResource, error) { diff --git a/pkg/tests/apis/provisioning/files_test.go b/pkg/tests/apis/provisioning/files_test.go index 823241aa6b0..e31674be505 100644 --- a/pkg/tests/apis/provisioning/files_test.go +++ b/pkg/tests/apis/provisioning/files_test.go @@ -1,7 +1,9 @@ package provisioning import ( + "bytes" "context" + "encoding/json" "errors" "fmt" "io" @@ -11,6 +13,7 @@ import ( "testing" "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions" "github.com/grafana/grafana/pkg/util/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -590,3 +593,390 @@ func TestIntegrationProvisioning_FilesOwnershipProtection(t *testing.T) { require.Equal(t, repo2, dashboard2.GetAnnotations()[utils.AnnoKeyManagerIdentity], "repo2's dashboard should still be owned by repo2") }) } + +func TestIntegrationProvisioning_FilesAuthorization(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + helper := runGrafana(t) + ctx := context.Background() + + const repo = "auth-test-repo" + helper.CreateRepo(t, TestRepo{ + Name: repo, + Path: helper.ProvisioningPath, + Target: "instance", + Copies: map[string]string{ + "testdata/all-panels.json": "dashboard1.json", + }, + ExpectedDashboards: 1, + ExpectedFolders: 0, + }) + + // Wait for initial sync to complete + var dashboardUID string + require.EventuallyWithT(t, func(collect *assert.CollectT) { + dashboards, err := helper.DashboardsV1.Resource.List(t.Context(), metav1.ListOptions{}) + if err != nil { + collect.Errorf("could not list dashboards error: %s", err.Error()) + return + } + if len(dashboards.Items) != 1 { + collect.Errorf("should have the expected dashboards after sync. got: %d. expected: %d", len(dashboards.Items), 1) + return + } + assert.Len(collect, dashboards.Items, 1) + dashboardUID = dashboards.Items[0].GetName() + }, waitTimeoutDefault, waitIntervalDefault, "should have the expected dashboards after sync") + + // Grant permissions to Editor user for all dashboards using wildcard + // The access checker checks resource-level permissions, so we need to grant them + // Using wildcard "*" to grant permissions to all dashboards (including ones created during tests) + // Note: Viewer role gets permissions via HTTP API below, Editor gets them here via SetPermissions + helper.SetPermissions(helper.Org1.Editor, []resourcepermissions.SetResourcePermissionCommand{ + { + Actions: []string{"dashboards:read", "dashboards:write", "dashboards:delete"}, + Resource: "dashboards", + ResourceAttribute: "uid", + ResourceID: "*", + }, + }) + + // Grant view permission to Viewer role via HTTP API (for the initial dashboard) + // Note: This only grants permissions to the initial dashboard, but viewers should be able to read all + addr := helper.GetEnv().Server.HTTPServer.Listener.Addr().String() + setDashboardPermissions := func(permissions []map[string]interface{}) { + payload := map[string]interface{}{ + "items": permissions, + } + payloadBytes, err := json.Marshal(payload) + require.NoError(t, err) + url := fmt.Sprintf("http://admin:admin@%s/api/dashboards/uid/%s/permissions", addr, dashboardUID) + req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(payloadBytes)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) + require.NoError(t, resp.Body.Close()) + } + + // Grant view permission to Viewer role for the initial dashboard + setDashboardPermissions([]map[string]interface{}{ + {"role": "Viewer", "permission": 1}, // View permission + }) + + t.Run("GET operations", func(t *testing.T) { + t.Run("viewer can GET files", func(t *testing.T) { + var statusCode int + result := helper.ViewerREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "dashboard1.json"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "viewer should be able to GET files") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) + + t.Run("editor can GET files", func(t *testing.T) { + var statusCode int + result := helper.EditorREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "dashboard1.json"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "editor should be able to GET files") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) + + t.Run("admin can GET files", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "dashboard1.json"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "admin should be able to GET files") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) + }) + + t.Run("POST operations", func(t *testing.T) { + t.Run("viewer cannot POST files", func(t *testing.T) { + var statusCode int + result := helper.ViewerREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "viewer-test.json"). + Body(helper.LoadFile("testdata/text-options.json")). + SetHeader("Content-Type", "application/json"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "viewer should not be able to POST files") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + + t.Run("editor can POST files", func(t *testing.T) { + var statusCode int + result := helper.EditorREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "editor-test.json"). + Body(helper.LoadFile("testdata/text-options.json")). + SetHeader("Content-Type", "application/json"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "editor should be able to POST files") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + + // Clean up + helper.AdminREST.Delete(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "editor-test.json"). + Do(ctx) + }) + + t.Run("admin can POST files", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "admin-test.json"). + Body(helper.LoadFile("testdata/text-options.json")). + SetHeader("Content-Type", "application/json"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "admin should be able to POST files") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + + // Clean up + helper.AdminREST.Delete(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "admin-test.json"). + Do(ctx) + }) + }) + + t.Run("PUT operations", func(t *testing.T) { + // Create a test file first using admin + helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "update-test.json"). + Body(helper.LoadFile("testdata/text-options.json")). + SetHeader("Content-Type", "application/json"). + Do(ctx) + + t.Run("viewer cannot PUT files", func(t *testing.T) { + var statusCode int + result := helper.ViewerREST.Put(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "update-test.json"). + Body(helper.LoadFile("testdata/timeline-demo.json")). + SetHeader("Content-Type", "application/json"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "viewer should not be able to PUT files") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + + t.Run("editor can PUT files", func(t *testing.T) { + var statusCode int + result := helper.EditorREST.Put(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "update-test.json"). + Body(helper.LoadFile("testdata/timeline-demo.json")). + SetHeader("Content-Type", "application/json"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "editor should be able to PUT files") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) + + t.Run("admin can PUT files", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Put(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "update-test.json"). + Body(helper.LoadFile("testdata/text-options.json")). + SetHeader("Content-Type", "application/json"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "admin should be able to PUT files") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) + + // Clean up + helper.AdminREST.Delete(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "update-test.json"). + Do(ctx) + }) + + t.Run("DELETE operations", func(t *testing.T) { + // Create test files for deletion tests + helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "delete-viewer-test.json"). + Body(helper.LoadFile("testdata/text-options.json")). + SetHeader("Content-Type", "application/json"). + Do(ctx) + + helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "delete-editor-test.json"). + Body(helper.LoadFile("testdata/text-options.json")). + SetHeader("Content-Type", "application/json"). + Do(ctx) + + helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "delete-admin-test.json"). + Body(helper.LoadFile("testdata/text-options.json")). + SetHeader("Content-Type", "application/json"). + Do(ctx) + + t.Run("viewer cannot DELETE files", func(t *testing.T) { + var statusCode int + result := helper.ViewerREST.Delete(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "delete-viewer-test.json"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "viewer should not be able to DELETE files") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + + // Verify file still exists + _, err := helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "delete-viewer-test.json") + require.NoError(t, err, "file should still exist after failed delete") + }) + + t.Run("editor can DELETE files", func(t *testing.T) { + var statusCode int + result := helper.EditorREST.Delete(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "delete-editor-test.json"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "editor should be able to DELETE files") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + + // Verify file was deleted + _, err := helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "delete-editor-test.json") + require.Error(t, err, "file should be deleted") + require.True(t, apierrors.IsNotFound(err), "should return NotFound for deleted file") + }) + + t.Run("admin can DELETE files", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Delete(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("files", "delete-admin-test.json"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "admin should be able to DELETE files") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + + // Verify file was deleted + _, err := helper.Repositories.Resource.Get(ctx, repo, metav1.GetOptions{}, "files", "delete-admin-test.json") + require.Error(t, err, "file should be deleted") + require.True(t, apierrors.IsNotFound(err), "should return NotFound for deleted file") + }) + }) + + t.Run("folder operations", func(t *testing.T) { + t.Run("viewer cannot create folders", func(t *testing.T) { + // Create a folder by POSTing to a directory path + addr := helper.GetEnv().Server.HTTPServer.Listener.Addr().String() + url := fmt.Sprintf("http://viewer:viewer@%s/apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/%s/files/test-folder/", addr, repo) + req, err := http.NewRequest(http.MethodPost, url, nil) + require.NoError(t, err) + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + // nolint:errcheck + defer resp.Body.Close() + + require.Equal(t, http.StatusForbidden, resp.StatusCode, "viewer should not be able to create folders") + }) + + t.Run("editor can create folders", func(t *testing.T) { + addr := helper.GetEnv().Server.HTTPServer.Listener.Addr().String() + url := fmt.Sprintf("http://editor:editor@%s/apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/%s/files/editor-folder/", addr, repo) + req, err := http.NewRequest(http.MethodPost, url, nil) + require.NoError(t, err) + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + // nolint:errcheck + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode, "editor should be able to create folders") + + // Clean up - delete folder + deleteURL := fmt.Sprintf("http://admin:admin@%s/apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/%s/files/editor-folder/", addr, repo) + deleteReq, err := http.NewRequest(http.MethodDelete, deleteURL, nil) + require.NoError(t, err) + deleteResp, err := http.DefaultClient.Do(deleteReq) + require.NoError(t, err) + // nolint:errcheck + defer deleteResp.Body.Close() + }) + + t.Run("admin can create folders", func(t *testing.T) { + addr := helper.GetEnv().Server.HTTPServer.Listener.Addr().String() + url := fmt.Sprintf("http://admin:admin@%s/apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/%s/files/admin-folder/", addr, repo) + req, err := http.NewRequest(http.MethodPost, url, nil) + require.NoError(t, err) + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + // nolint:errcheck + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode, "admin should be able to create folders") + + // Clean up - delete folder + deleteURL := fmt.Sprintf("http://admin:admin@%s/apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/%s/files/admin-folder/", addr, repo) + deleteReq, err := http.NewRequest(http.MethodDelete, deleteURL, nil) + require.NoError(t, err) + deleteResp, err := http.DefaultClient.Do(deleteReq) + require.NoError(t, err) + // nolint:errcheck + defer deleteResp.Body.Close() + }) + }) +} From bf2682712f2119a55549c7bb9744ae114d0894d1 Mon Sep 17 00:00:00 2001 From: Daniele Stefano Ferru Date: Tue, 16 Dec 2025 18:31:53 +0100 Subject: [PATCH 163/297] Provisioning: Add Connection reference to Repository resource (#115419) * Provisioning: Add Connection reference to Repository resource * addressing comments --- apps/provisioning/kinds/repository.cue | 8 ++++- .../pkg/apis/provisioning/v0alpha1/types.go | 8 +++++ .../v0alpha1/zz_generated.deepcopy.go | 21 ++++++++++++++ .../v0alpha1/zz_generated.openapi.go | 29 ++++++++++++++++++- .../provisioning/v0alpha1/connectioninfo.go | 25 ++++++++++++++++ .../provisioning/v0alpha1/repositoryspec.go | 9 ++++++ .../pkg/generated/applyconfiguration/utils.go | 2 ++ .../provisioning/v0alpha1/endpoints.gen.ts | 5 ++++ .../provisioning.grafana.app-v0alpha1.json | 20 +++++++++++++ 9 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectioninfo.go diff --git a/apps/provisioning/kinds/repository.cue b/apps/provisioning/kinds/repository.cue index ba36b4b962f..fec3d1f9e20 100644 --- a/apps/provisioning/kinds/repository.cue +++ b/apps/provisioning/kinds/repository.cue @@ -84,6 +84,9 @@ repository: { // When non-zero, the sync will run periodically intervalSeconds?: int } + #ConnectionInfo: { + name: string + } #HealthStatus: { // When not healthy, requests will not be executed healthy: bool @@ -152,6 +155,9 @@ repository: { // The repository on GitLab. // Mutually exclusive with local | github | git. gitlab?: #GitLabRepositoryConfig + // The connection the repository references. + // This means the Repository is interacting with git via a Connection. + connection?: #ConnectionInfo } status: { // The generation of the spec last time reconciliation ran @@ -168,4 +174,4 @@ repository: { } } } -} \ No newline at end of file +} diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go index a4c4f0ef643..c2247e21809 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go @@ -219,6 +219,10 @@ func (r *Repository) Path() string { return "" } +type ConnectionInfo struct { + Name string `json:"name"` +} + type RepositorySpec struct { // The repository display name (shown in the UI) Title string `json:"title"` @@ -256,6 +260,10 @@ type RepositorySpec struct { // The repository on GitLab. // Mutually exclusive with local | github | git. GitLab *GitLabRepositoryConfig `json:"gitlab,omitempty"` + + // The connection the repository references. + // This means the Repository is interacting with git via a Connection. + Connection *ConnectionInfo `json:"connection,omitempty"` } // SyncTargetType defines where we want all values to resolve diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go index 6ca50fdd277..48d03d23f99 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go @@ -88,6 +88,22 @@ func (in *Connection) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConnectionInfo) DeepCopyInto(out *ConnectionInfo) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectionInfo. +func (in *ConnectionInfo) DeepCopy() *ConnectionInfo { + if in == nil { + return nil + } + out := new(ConnectionInfo) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConnectionList) DeepCopyInto(out *ConnectionList) { *out = *in @@ -922,6 +938,11 @@ func (in *RepositorySpec) DeepCopyInto(out *RepositorySpec) { *out = new(GitLabRepositoryConfig) **out = **in } + if in.Connection != nil { + in, out := &in.Connection, &out.Connection + *out = new(ConnectionInfo) + **out = **in + } return } diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go index 0e1a224d457..aeee1d407e1 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go @@ -18,6 +18,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.BitbucketConnectionConfig": schema_pkg_apis_provisioning_v0alpha1_BitbucketConnectionConfig(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.BitbucketRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_BitbucketRepositoryConfig(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.Connection": schema_pkg_apis_provisioning_v0alpha1_Connection(ref), + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionInfo": schema_pkg_apis_provisioning_v0alpha1_ConnectionInfo(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionList": schema_pkg_apis_provisioning_v0alpha1_ConnectionList(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionSecure": schema_pkg_apis_provisioning_v0alpha1_ConnectionSecure(ref), "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionSpec": schema_pkg_apis_provisioning_v0alpha1_ConnectionSpec(ref), @@ -224,6 +225,26 @@ func schema_pkg_apis_provisioning_v0alpha1_Connection(ref common.ReferenceCallba } } +func schema_pkg_apis_provisioning_v0alpha1_ConnectionInfo(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + func schema_pkg_apis_provisioning_v0alpha1_ConnectionList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -1868,12 +1889,18 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositorySpec(ref common.ReferenceCa Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitLabRepositoryConfig"), }, }, + "connection": { + SchemaProps: spec.SchemaProps{ + Description: "The connection the repository references. This means the Repository is interacting with git via a Connection.", + Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionInfo"), + }, + }, }, Required: []string{"title", "workflows", "sync", "type"}, }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.BitbucketRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitHubRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitLabRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.LocalRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncOptions"}, + "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.BitbucketRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.ConnectionInfo", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitHubRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitLabRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.GitRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.LocalRepositoryConfig", "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.SyncOptions"}, } } diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectioninfo.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectioninfo.go new file mode 100644 index 00000000000..87e4aa7e180 --- /dev/null +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/connectioninfo.go @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +// ConnectionInfoApplyConfiguration represents a declarative configuration of the ConnectionInfo type for use +// with apply. +type ConnectionInfoApplyConfiguration struct { + Name *string `json:"name,omitempty"` +} + +// ConnectionInfoApplyConfiguration constructs a declarative configuration of the ConnectionInfo type for use with +// apply. +func ConnectionInfo() *ConnectionInfoApplyConfiguration { + return &ConnectionInfoApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *ConnectionInfoApplyConfiguration) WithName(value string) *ConnectionInfoApplyConfiguration { + b.Name = &value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositoryspec.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositoryspec.go index 6fff6f2de42..5caf805702b 100644 --- a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositoryspec.go +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositoryspec.go @@ -21,6 +21,7 @@ type RepositorySpecApplyConfiguration struct { Git *GitRepositoryConfigApplyConfiguration `json:"git,omitempty"` Bitbucket *BitbucketRepositoryConfigApplyConfiguration `json:"bitbucket,omitempty"` GitLab *GitLabRepositoryConfigApplyConfiguration `json:"gitlab,omitempty"` + Connection *ConnectionInfoApplyConfiguration `json:"connection,omitempty"` } // RepositorySpecApplyConfiguration constructs a declarative configuration of the RepositorySpec type for use with @@ -110,3 +111,11 @@ func (b *RepositorySpecApplyConfiguration) WithGitLab(value *GitLabRepositoryCon b.GitLab = value return b } + +// WithConnection sets the Connection field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Connection field is set to the value of the last call. +func (b *RepositorySpecApplyConfiguration) WithConnection(value *ConnectionInfoApplyConfiguration) *RepositorySpecApplyConfiguration { + b.Connection = value + return b +} diff --git a/apps/provisioning/pkg/generated/applyconfiguration/utils.go b/apps/provisioning/pkg/generated/applyconfiguration/utils.go index 52c3faee58c..7168418b82d 100644 --- a/apps/provisioning/pkg/generated/applyconfiguration/utils.go +++ b/apps/provisioning/pkg/generated/applyconfiguration/utils.go @@ -24,6 +24,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &provisioningv0alpha1.BitbucketRepositoryConfigApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("Connection"): return &provisioningv0alpha1.ConnectionApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("ConnectionInfo"): + return &provisioningv0alpha1.ConnectionInfoApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("ConnectionSecure"): return &provisioningv0alpha1.ConnectionSecureApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("ConnectionSpec"): diff --git a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts index cb2aa3d08e2..3bb583d7cb4 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts @@ -1731,6 +1731,9 @@ export type BitbucketRepositoryConfig = { /** The repository URL (e.g. `https://bitbucket.org/example/test`). */ url?: string; }; +export type ConnectionInfo = { + name: string; +}; export type GitRepositoryConfig = { /** The branch to use in the repository. */ branch: string; @@ -1783,6 +1786,8 @@ export type SyncOptions = { export type RepositorySpec = { /** The repository on Bitbucket. Mutually exclusive with local | github | git. */ bitbucket?: BitbucketRepositoryConfig; + /** The connection the repository references. This means the Repository is interacting with git via a Connection. */ + connection?: ConnectionInfo; /** Repository description */ description?: string; /** The repository on Git. Mutually exclusive with local | github | git. */ diff --git a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json index 0eeb7737af2..8952906ac29 100644 --- a/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/provisioning.grafana.app-v0alpha1.json @@ -4409,6 +4409,18 @@ } ] }, + "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ConnectionInfo": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "default": "" + } + } + }, "com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ConnectionList": { "type": "object", "required": [ @@ -5525,6 +5537,14 @@ } ] }, + "connection": { + "description": "The connection the repository references. This means the Repository is interacting with git via a Connection.", + "allOf": [ + { + "$ref": "#/components/schemas/com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.ConnectionInfo" + } + ] + }, "description": { "description": "Repository description", "type": "string" From 26487fb864c5bc39f6b1112854c9e4c6535fa2a0 Mon Sep 17 00:00:00 2001 From: Matthew Jacobson Date: Tue, 16 Dec 2025 13:34:41 -0500 Subject: [PATCH 164/297] Alerting: Improve ASH Loki query efficiency by including folderUID (#113322) * Alerting: Improve ASH Loki query efficiency by including folderUID Previously, the folderUID label was only included when ruleUID was not specified and the user did not have full alert rule read permissions. To improve ASH Loki query efficiency, this PR includes the folderUID in the ASH Loki query when ruleUID is specified, even if the user has full alert rule read permissions. Some non-obvious considerations: - The naive implementation of just including the current folder UID would have the unintended side-effect of no longer returning history after a rule is moved between folders. - The previous implementation made the trade-off of only checking RBAC on the current folder, including any history from old folders that may exist. To solve both of the above, we make an extra query to the database to check the alert rule's previous versions so we can include any old folderUIDs, checking RBAC at the same time. The querying and inclusion of history from old folders is done best-effort, any issues that might arise are logged and ignored so as not to prevent the current folder history. * Fix merge conflicts * Reduce scanning on GetAlertRuleVersionFolders by grouping in query --- .../ngalert/state/historian/annotation.go | 1 + pkg/services/ngalert/state/historian/loki.go | 97 ++++++++-- .../ngalert/state/historian/loki_test.go | 177 +++++++++++++++++- pkg/services/ngalert/store/alert_rule.go | 21 +++ pkg/services/ngalert/store/alert_rule_test.go | 49 +++++ pkg/services/ngalert/tests/fakes/rules.go | 25 +++ 6 files changed, 351 insertions(+), 19 deletions(-) diff --git a/pkg/services/ngalert/state/historian/annotation.go b/pkg/services/ngalert/state/historian/annotation.go index d2353ec42d8..d9cdfaa9d2b 100644 --- a/pkg/services/ngalert/state/historian/annotation.go +++ b/pkg/services/ngalert/state/historian/annotation.go @@ -44,6 +44,7 @@ type AnnotationBackend struct { type RuleStore interface { GetAlertRuleByUID(ctx context.Context, query *ngmodels.GetAlertRuleByUIDQuery) (*ngmodels.AlertRule, error) GetUserVisibleNamespaces(ctx context.Context, orgID int64, user identity.Requester) (map[string]*folder.Folder, error) + GetAlertRuleVersionFolders(ctx context.Context, orgID int64, guid string) ([]string, error) } type AnnotationStore interface { diff --git a/pkg/services/ngalert/state/historian/loki.go b/pkg/services/ngalert/state/historian/loki.go index e07461f27bf..76e3e9025bd 100644 --- a/pkg/services/ngalert/state/historian/loki.go +++ b/pkg/services/ngalert/state/historian/loki.go @@ -505,30 +505,25 @@ func (h *RemoteLokiBackend) getFolderUIDsForFilter(ctx context.Context, query mo if err != nil { return nil, err } - if bypass { // if user has access to all rules and folder, remove filter + + if query.RuleUID != "" { + return h.getFolderUIDsForRuleFilter(ctx, query, bypass) + } + + // If the query has no rule filter, we need to return all folder UIDs the user has access to. + // For a user with access to all rules and folders, the full list of folders will likely be too large to be an + // effective optimization in Loki, so we skip folderUID filtering entirely in that case. + if bypass { return nil, nil } - // if there is a filter by rule UID, find that rule UID and make sure that user has access to it. - if query.RuleUID != "" { - rule, err := h.ruleStore.GetAlertRuleByUID(ctx, &models.GetAlertRuleByUIDQuery{ - UID: query.RuleUID, - OrgID: query.OrgID, - }) - if err != nil { - return nil, fmt.Errorf("failed to fetch alert rule by UID: %w", err) - } - if rule == nil { - return nil, models.ErrAlertRuleNotFound - } - return nil, h.ac.AuthorizeAccessInFolder(ctx, query.SignedInUser, rule) - } - // if no filter, then we need to get all namespaces user has access to + + // All folders the user has access to. folders, err := h.ruleStore.GetUserVisibleNamespaces(ctx, query.OrgID, query.SignedInUser) if err != nil { return nil, fmt.Errorf("failed to fetch folders that user can access: %w", err) } uids := make([]string, 0, len(folders)) - // now keep only UIDs of folder in which user can read rules. + // Keep only UIDs of folder in which user can read rules. for _, f := range folders { hasAccess, err := h.ac.HasAccessInFolder(ctx, query.SignedInUser, models.NewNamespace(f)) if err != nil { @@ -545,3 +540,71 @@ func (h *RemoteLokiBackend) getFolderUIDsForFilter(ctx context.Context, query mo sort.Strings(uids) return uids, nil } + +func (h *RemoteLokiBackend) getFolderUIDsForRuleFilter(ctx context.Context, query models.HistoryQuery, canReadAll bool) ([]string, error) { + rule, err := h.ruleStore.GetAlertRuleByUID(ctx, &models.GetAlertRuleByUIDQuery{ + UID: query.RuleUID, + OrgID: query.OrgID, + }) + if err != nil { + if canReadAll { + // When the user can read all rules, filtering by folder UID is purely an optimization, so we can ignore errors here. + h.log.FromContext(ctx).Debug("failed to fetch alert rule by UID", "err", err) + return nil, nil + } + return nil, fmt.Errorf("failed to fetch alert rule by UID: %w", err) + } + + // First, we check if the user has access to the current version of the rule. If not, we can return early. + // Whether we should check historical folders they might still have access to is not 100% clear, but it seems more + // intuitive to deny access in this case. + if !canReadAll { + if err := h.ac.AuthorizeAccessInFolder(ctx, query.SignedInUser, rule); err != nil { + return nil, err + } + } + + // We want to return folder UIDs when possible, as it's indexed in Loki and will help with query performance. + // However, by just returning the current folder UID the user can lose history when a rule is moved between folders. + // So, we attempt to get historical folder UIDs from the rule's history. + historicalFolders, err := h.ruleStore.GetAlertRuleVersionFolders(ctx, rule.OrgID, rule.GUID) + if err != nil { + // Including historical folders is an edge case enhancement, better to just log the error and continue + // with the current folder UID. + h.log.FromContext(ctx).Debug("failed to include historical folder UIDs for rule", "err", err) + } + + accessibleFolders := make([]string, 0, len(historicalFolders)+1) + dedup := make(map[string]struct{}) + + accessibleFolders = append(accessibleFolders, rule.GetNamespaceUID()) + dedup[rule.GetNamespaceUID()] = struct{}{} + + for _, folderUID := range historicalFolders { + if _, exists := dedup[folderUID]; exists { + continue + } + + if canReadAll { + // If the user can read all rules, no need to check access to each folder. + accessibleFolders = append(accessibleFolders, folderUID) + continue + } + + hasAccess, err := h.ac.HasAccessInFolder(ctx, query.SignedInUser, models.Namespace{ + UID: folderUID, + }) + if err != nil { + // Including historical folders is an edge case enhancement, better to just log the error and continue + // with the current folder UID. + h.log.FromContext(ctx).Debug("failed to check access to folder", "err", err, "folderUID", folderUID) + continue + } + if !hasAccess { + continue + } + accessibleFolders = append(accessibleFolders, folderUID) + } + + return accessibleFolders, nil +} diff --git a/pkg/services/ngalert/state/historian/loki_test.go b/pkg/services/ngalert/state/historian/loki_test.go index a01efe9563b..d5cd482d177 100644 --- a/pkg/services/ngalert/state/historian/loki_test.go +++ b/pkg/services/ngalert/state/historian/loki_test.go @@ -22,6 +22,7 @@ import ( alertingInstrument "github.com/grafana/alerting/http/instrument" "github.com/grafana/alerting/http/instrument/instrumenttest" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" @@ -868,7 +869,8 @@ func TestGetFolderUIDsForFilter(t *testing.T) { } result, err := createLoki(ac).getFolderUIDsForFilter(context.Background(), models.HistoryQuery{OrgID: orgID, RuleUID: rule.UID, SignedInUser: usr}) assert.NoError(t, err) - assert.Empty(t, result) + assert.Len(t, result, 1) + assert.Contains(t, result, rule.GetNamespaceUID()) assert.Len(t, ac.Calls, 1) assert.Equal(t, "CanReadAllRules", ac.Calls[0].MethodName) @@ -893,7 +895,8 @@ func TestGetFolderUIDsForFilter(t *testing.T) { result, err := loki.getFolderUIDsForFilter(context.Background(), models.HistoryQuery{OrgID: orgID, RuleUID: rule.UID, SignedInUser: usr}) assert.NoError(t, err) - assert.Empty(t, result) + assert.Len(t, result, 1) + assert.Contains(t, result, rule.GetNamespaceUID()) assert.Len(t, ac.Calls, 2) assert.Equal(t, "CanReadAllRules", ac.Calls[0].MethodName) @@ -916,6 +919,21 @@ func TestGetFolderUIDsForFilter(t *testing.T) { require.ErrorIs(t, err, models.ErrAlertRuleNotFound) }) }) + + t.Run("should return folderUID", func(t *testing.T) { + for _, authBypass := range []bool{true, false} { + t.Run(fmt.Sprintf("authBypass=%v", authBypass), func(t *testing.T) { + ac := &acfakes.FakeRuleService{} + ac.CanReadAllRulesFunc = func(ctx context.Context, requester identity.Requester) (bool, error) { + return authBypass, nil + } + result, err := createLoki(ac).getFolderUIDsForFilter(context.Background(), models.HistoryQuery{OrgID: orgID, RuleUID: rule.UID, SignedInUser: usr}) + assert.NoError(t, err) + assert.Len(t, result, 1) + assert.Contains(t, result, rule.GetNamespaceUID()) + }) + } + }) }) t.Run("when rule UID is empty", func(t *testing.T) { @@ -982,6 +1000,161 @@ func TestGetFolderUIDsForFilter(t *testing.T) { }) } +func TestGetFolderUIDsForFilterWithHistoricalFolders(t *testing.T) { + // Simple history generator to avoid repetitive code in test cases. + simpleHistory := func(guid string) map[string][]*models.AlertRuleVersion { + return map[string][]*models.AlertRuleVersion{ + guid: { + &models.AlertRuleVersion{AlertRule: models.RuleGen.With(models.RuleMuts.WithGUID(guid), models.RuleMuts.WithNamespaceUID("folder-current")).Generate()}, + &models.AlertRuleVersion{AlertRule: models.RuleGen.With(models.RuleMuts.WithGUID(guid), models.RuleMuts.WithNamespaceUID("folder-historical-1")).Generate()}, + &models.AlertRuleVersion{AlertRule: models.RuleGen.With(models.RuleMuts.WithGUID(guid), models.RuleMuts.WithNamespaceUID("folder-historical-2")).Generate()}, + &models.AlertRuleVersion{AlertRule: models.RuleGen.With(models.RuleMuts.WithGUID(guid), models.RuleMuts.WithNamespaceUID("folder-historical-3")).Generate()}, + }, + } + } + + // Helper to create simple folder access override functions. + canReadRulesInFolders := func(folderUids ...string) func(folderUID string) (bool, error) { + return func(folderUID string) (bool, error) { + for _, f := range folderUids { + if folderUID == f { + return true, nil + } + } + return false, nil + } + } + + // Helper to fail historical folders query. + failHistoryQueryHook := func(cmd any) error { + q, ok := cmd.(fakes.GenericRecordedQuery) + if !ok { + return nil + } + if q.Name == "GetAlertRuleVersionFolders" { + return errors.New("generic error") + } + return nil + } + + cases := []struct { + name string + // Setup. + existingHistory map[string][]*models.AlertRuleVersion + canReadAllRules bool + rule *models.AlertRule + + // Error overrides. + ruleStoreHook func(cmd any) error + folderAccessOverride func(folderUID string) (bool, error) + + // Expected. + expectedFolders []string + }{ + { + name: "should include historical folders when user can read all rules", + existingHistory: simpleHistory("guid-1"), + canReadAllRules: true, + rule: models.RuleGen.With(models.RuleMuts.WithGUID("guid-1"), models.RuleMuts.WithNamespaceUID("folder-current")).GenerateRef(), + expectedFolders: []string{"folder-current", "folder-historical-1", "folder-historical-2", "folder-historical-3"}, + }, + { + name: "should include only authorized historical folders", + existingHistory: simpleHistory("guid-1"), + folderAccessOverride: canReadRulesInFolders("folder-current", "folder-historical-2"), + rule: models.RuleGen.With(models.RuleMuts.WithGUID("guid-1"), models.RuleMuts.WithNamespaceUID("folder-current")).GenerateRef(), + expectedFolders: []string{"folder-current", "folder-historical-2"}, + }, + { + name: "if historical folders query fails, should return current folder", + existingHistory: simpleHistory("guid-1"), + canReadAllRules: true, + rule: models.RuleGen.With(models.RuleMuts.WithGUID("guid-1"), models.RuleMuts.WithNamespaceUID("folder-current")).GenerateRef(), + ruleStoreHook: failHistoryQueryHook, + expectedFolders: []string{"folder-current"}, + }, + { + name: "if historical folders query fails, should return current folder", + existingHistory: simpleHistory("guid-1"), + folderAccessOverride: canReadRulesInFolders("folder-current", "folder-historical-2"), + rule: models.RuleGen.With(models.RuleMuts.WithGUID("guid-1"), models.RuleMuts.WithNamespaceUID("folder-current")).GenerateRef(), + ruleStoreHook: failHistoryQueryHook, + expectedFolders: []string{"folder-current"}, + }, + { + name: "if access check for historical folders fails, should ignore", + existingHistory: simpleHistory("guid-1"), + rule: models.RuleGen.With(models.RuleMuts.WithGUID("guid-1"), models.RuleMuts.WithNamespaceUID("folder-current")).GenerateRef(), + folderAccessOverride: func(folderUID string) (bool, error) { + switch folderUID { + case "folder-current", "folder-historical-3": + return true, nil + case "folder-historical-2": + return false, nil + case "folder-historical-1": + return false, errors.New("generic error") + } + return false, nil + }, + expectedFolders: []string{"folder-current", "folder-historical-3"}, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Setup. + orgID := int64(1) + usr := accesscontrol.BackgroundUser("test", 1, org.RoleNone, nil) + + ac := &acfakes.FakeRuleService{} + ac.CanReadAllRulesFunc = func(ctx context.Context, requester identity.Requester) (bool, error) { + return tc.canReadAllRules, nil + } + ac.AuthorizeAccessInFolderFunc = func(ctx context.Context, requester identity.Requester, namespaced models.Namespaced) error { + if tc.canReadAllRules { + return nil + } + hasAccess, err := tc.folderAccessOverride(namespaced.GetNamespaceUID()) + if err != nil { + return err + } + if !hasAccess { + return rulesAuthz.ErrAuthorizationBase.Errorf("%w", err) + } + return nil + } + ac.HasAccessInFolderFunc = func(ctx context.Context, requester identity.Requester, namespaced models.Namespaced) (bool, error) { + if tc.canReadAllRules { + return true, nil + } + return tc.folderAccessOverride(namespaced.GetNamespaceUID()) + } + + rulesStore := fakes.NewRuleStore(t) + rulesStore.Rules = map[int64][]*models.AlertRule{ + orgID: { + tc.rule, + // Add some irrelevant rules to ensure they are ignored. + models.RuleGen.With(models.RuleMuts.WithNamespaceUID(tc.rule.GetNamespaceUID())).GenerateRef(), + models.RuleGen.With(models.RuleMuts.WithNamespaceUID("irrelevant-folder")).GenerateRef(), + }, + } + rulesStore.History = tc.existingHistory + if tc.ruleStoreHook != nil { + rulesStore.Hook = tc.ruleStoreHook + } + + loki := createTestLokiBackend(t, instrumenttest.NewFakeRequester(), metrics.NewHistorianMetrics(prometheus.NewRegistry(), metrics.Subsystem)) + loki.ruleStore = rulesStore + loki.ac = ac + + // Test conditions. + result, err := loki.getFolderUIDsForFilter(context.Background(), models.HistoryQuery{OrgID: orgID, RuleUID: tc.rule.UID, SignedInUser: usr}) + assert.NoError(t, err) + assert.ElementsMatch(t, tc.expectedFolders, result) + }) + } +} + func createTestLokiBackend(t *testing.T, req alertingInstrument.Requester, met *metrics.Historian) *RemoteLokiBackend { url, _ := url.Parse("http://some.url") cfg := lokiclient.LokiConfig{ diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index 4307778f9c6..5957e7026b2 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -239,6 +239,27 @@ func (st DBstore) GetAlertRuleVersions(ctx context.Context, orgID int64, guid st return alertRules, nil } +// GetAlertRuleVersionFolders retrieves a list of unique folder UIDs that the given rule guid has belonged to. +// Returned slice is ordered with more recent folders first. +func (st DBstore) GetAlertRuleVersionFolders(ctx context.Context, orgID int64, guid string) ([]string, error) { + folders := make([]string, 0) + err := st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error { + if err := sess.Table(new(alertRuleVersion)). + Select("rule_namespace_uid"). + Where("rule_org_id = ? AND rule_guid = ?", orgID, guid). + GroupBy("rule_namespace_uid"). + OrderBy("MAX(version) DESC"). + Find(&folders); err != nil { + return err + } + return nil + }) + if err != nil { + return nil, err + } + return folders, nil +} + // ListDeletedRules retrieves a list of deleted alert rules for the specified organization ID from the database. // It ensures that only the latest version of each rule is included and filters out invalid or duplicated versions. // Returns a slice of *models.AlertRule or an error if the operation fails. diff --git a/pkg/services/ngalert/store/alert_rule_test.go b/pkg/services/ngalert/store/alert_rule_test.go index 91db9edc32a..29b82f943ba 100644 --- a/pkg/services/ngalert/store/alert_rule_test.go +++ b/pkg/services/ngalert/store/alert_rule_test.go @@ -1727,6 +1727,55 @@ func TestIntegrationGetRuleVersions(t *testing.T) { }) } +func TestIntegrationGetAlertRuleVersionFolders(t *testing.T) { + tutil.SkipIntegrationTestInShortMode(t) + + // Setup. + cfg := setting.NewCfg() + cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{BaseInterval: time.Duration(rand.Int64N(100)+1) * time.Second} + sqlStore := db.InitTestDB(t) + folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()) + b := &fakeBus{} + store := createTestStore(sqlStore, folderService, &logtest.Fake{}, cfg.UnifiedAlerting, b) + orgID := int64(1) + gen := models.RuleGen + gen = gen.With(gen.WithIntervalMatching(store.Cfg.BaseInterval), gen.WithOrgID(orgID), gen.WithVersion(1)) + + inserted, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, []models.InsertRule{{AlertRule: gen.Generate()}}) + require.NoError(t, err) + ruleV1, err := store.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{UID: inserted[0].UID}) + require.NoError(t, err) + + oldRule := ruleV1 + updatedRule := ruleV1 + updateRule := func(title string, folderUID string) { + oldRule = updatedRule + updatedRule = models.CopyRule(oldRule, gen.WithTitle(title), gen.WithNamespaceUID(folderUID)) + require.NoError(t, store.UpdateAlertRules(context.Background(), &models.AlertingUserUID, []models.UpdateRule{{Existing: oldRule, New: *updatedRule}})) + updatedRule.Version++ // Simulate version increment after update to avoid conflict errors. + } + + // Update rule a couple of times to create versions. + originalFolder := oldRule.NamespaceUID + updateRule(util.GenerateShortUID(), originalFolder) + updateRule(util.GenerateShortUID(), "newfolder-1") + updateRule(util.GenerateShortUID(), "newfolder-2") + updateRule(util.GenerateShortUID(), "newfolder-2") + updateRule(util.GenerateShortUID(), originalFolder) + updateRule(util.GenerateShortUID(), "current-folder") + + t.Run("should return rule versions folders sorted in decreasing order", func(t *testing.T) { + historicalFolders, err := store.GetAlertRuleVersionFolders(context.Background(), updatedRule.OrgID, updatedRule.GUID) + require.NoError(t, err) + assert.Equal(t, []string{ // Return folders with more recent first. + "current-folder", + originalFolder, + "newfolder-2", + "newfolder-1", + }, historicalFolders) + }) +} + // createAlertRule creates an alert rule in the database and returns it. // If a generator is not specified, uniqueness of primary key is not guaranteed. func createRule(tb testing.TB, store *DBstore, generator *models.AlertRuleGenerator) *models.AlertRule { diff --git a/pkg/services/ngalert/tests/fakes/rules.go b/pkg/services/ngalert/tests/fakes/rules.go index 42e30ddb052..f4b5afb282d 100644 --- a/pkg/services/ngalert/tests/fakes/rules.go +++ b/pkg/services/ngalert/tests/fakes/rules.go @@ -2,6 +2,7 @@ package fakes import ( "context" + "maps" "math/rand" "slices" "strings" @@ -612,6 +613,30 @@ func (f *RuleStore) GetAlertRuleVersions(_ context.Context, orgID int64, guid st return f.History[guid], nil } +func (f *RuleStore) GetAlertRuleVersionFolders(_ context.Context, orgID int64, guid string) ([]string, error) { + f.mtx.Lock() + defer f.mtx.Unlock() + + q := GenericRecordedQuery{ + Name: "GetAlertRuleVersionFolders", + Params: []any{orgID, guid}, + } + defer func() { + f.RecordedOps = append(f.RecordedOps, q) + }() + + if err := f.Hook(q); err != nil { + return nil, err + } + + folderSet := make(map[string]struct{}) + for _, rule := range f.History[guid] { + folderSet[rule.NamespaceUID] = struct{}{} + } + + return slices.Collect(maps.Keys(folderSet)), nil +} + func (f *RuleStore) ListDeletedRules(_ context.Context, orgID int64) ([]*models.AlertRule, error) { f.mtx.Lock() defer f.mtx.Unlock() From bd8f0083cec7355dcc9984d71df588b2b3c00e63 Mon Sep 17 00:00:00 2001 From: Renato Costa <103441181+renatolabs@users.noreply.github.com> Date: Tue, 16 Dec 2025 14:32:13 -0500 Subject: [PATCH 165/297] unified-storage: add `Get` support to the sqlkv implementation (#115382) --- apps/iam/go.sum | 2 + .../unified/resource/data/sqlkv_get.sql | 3 + pkg/storage/unified/resource/kv.go | 3 + pkg/storage/unified/resource/sqlkv.go | 115 +++++++++++++++++- pkg/storage/unified/testing/kv.go | 34 +++++- pkg/storage/unified/testing/kv_test.go | 1 - 6 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 pkg/storage/unified/resource/data/sqlkv_get.sql diff --git a/apps/iam/go.sum b/apps/iam/go.sum index cf4535fbe71..4c8ec5e38b5 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -150,6 +150,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/FZambia/eagle v0.2.0 h1:1kQaZpJvbkvAXFRE/9K2ucBMuVqo+E29EMLYB74hIis= github.com/FZambia/eagle v0.2.0/go.mod h1:LKMYBwGYhao5sJI0TppvQ4SvvldFj9gITxrl8NvGwG0= diff --git a/pkg/storage/unified/resource/data/sqlkv_get.sql b/pkg/storage/unified/resource/data/sqlkv_get.sql new file mode 100644 index 00000000000..48c83cf0450 --- /dev/null +++ b/pkg/storage/unified/resource/data/sqlkv_get.sql @@ -0,0 +1,3 @@ +SELECT {{ .Ident "value" | .Into .Value }} +FROM {{ .Ident .TableName }} +WHERE {{ .Ident "key_path" }} = {{ .Arg .KeyPath }}; diff --git a/pkg/storage/unified/resource/kv.go b/pkg/storage/unified/resource/kv.go index 043ea45f695..0af2cc3c4a1 100644 --- a/pkg/storage/unified/resource/kv.go +++ b/pkg/storage/unified/resource/kv.go @@ -87,6 +87,9 @@ func (k *badgerKV) Get(ctx context.Context, section string, key string) (io.Read if section == "" { return nil, fmt.Errorf("section is required") } + if key == "" { + return nil, fmt.Errorf("key is required") + } key = section + "/" + key diff --git a/pkg/storage/unified/resource/sqlkv.go b/pkg/storage/unified/resource/sqlkv.go index f6e8d0698f3..c943b5292f6 100644 --- a/pkg/storage/unified/resource/sqlkv.go +++ b/pkg/storage/unified/resource/sqlkv.go @@ -1,19 +1,101 @@ package resource import ( + "bytes" "context" + "database/sql" + "embed" + "errors" "fmt" "io" "iter" + "text/template" "github.com/grafana/grafana/pkg/storage/unified/sql/db" + "github.com/grafana/grafana/pkg/storage/unified/sql/dbutil" + "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" ) +// Templates setup. +var ( + //go:embed data/*.sql + sqlTemplatesFS embed.FS + + sqlTemplates = template.Must(template.New("sql").ParseFS(sqlTemplatesFS, `data/*.sql`)) +) + +func mustTemplate(filename string) *template.Template { + if t := sqlTemplates.Lookup(filename); t != nil { + return t + } + panic(fmt.Sprintf("template file not found: %s", filename)) +} + +// Templates. +var ( + sqlKVGet = mustTemplate("sqlkv_get.sql") +) + +type sqlKVSection struct { + Section string +} + +func (req sqlKVSection) Validate() error { + if req.Section == "" { + return fmt.Errorf("section is required") + } + + if req.Section != dataSection && req.Section != eventsSection { + return fmt.Errorf("invalid section: %s", req.Section) + } + + return nil +} + +func (req sqlKVSection) TableName() string { + if req.Section == dataSection { + return "resource_history" + } + + return "resource_events" +} + +type sqlKVGetRequest struct { + sqltemplate.SQLTemplate + sqlKVSection + Key string + *sqlKVGetResponse +} + +type sqlKVGetResponse struct { + Value []byte +} + +func (req sqlKVGetRequest) Validate() error { + if err := req.sqlKVSection.Validate(); err != nil { + return err + } + if req.Key == "" { + return fmt.Errorf("key is required") + } + + return nil +} + +func (req sqlKVGetRequest) Results() ([]byte, error) { + return req.Value, nil +} + +func (req sqlKVGetRequest) KeyPath() string { + return req.Section + "/" + req.Key +} + var _ KV = &sqlKV{} type sqlKV struct { dbProvider db.DBProvider db db.DB + dialect sqltemplate.Dialect } func NewSQLKV(dbProvider db.DBProvider) (KV, error) { @@ -27,9 +109,15 @@ func NewSQLKV(dbProvider db.DBProvider) (KV, error) { return nil, fmt.Errorf("error initializing DB: %w", err) } + dialect := sqltemplate.DialectForDriver(dbConn.DriverName()) + if dialect == nil { + return nil, fmt.Errorf("unsupported database driver: %s", dbConn.DriverName()) + } + return &sqlKV{ dbProvider: dbProvider, db: dbConn, + dialect: dialect, }, nil } @@ -44,7 +132,20 @@ func (k *sqlKV) Keys(ctx context.Context, section string, opt ListOptions) iter. } func (k *sqlKV) Get(ctx context.Context, section string, key string) (io.ReadCloser, error) { - panic("not implemented!") + value, err := dbutil.QueryRow(ctx, k.db, sqlKVGet, sqlKVGetRequest{ + SQLTemplate: sqltemplate.New(k.dialect), + sqlKVSection: sqlKVSection{section}, + Key: key, + sqlKVGetResponse: new(sqlKVGetResponse), + }) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrNotFound + } + return nil, fmt.Errorf("failed to get key: %w", err) + } + + return io.NopCloser(bytes.NewReader(value)), nil } func (k *sqlKV) BatchGet(ctx context.Context, section string, keys []string) iter.Seq2[KeyValue, error] { @@ -53,6 +154,18 @@ func (k *sqlKV) BatchGet(ctx context.Context, section string, keys []string) ite } } +// TODO: this function only exists to support the testing of the sqlkv implementation before +// we have a proper implementation of `Save`. +func (k *sqlKV) TestingSave(ctx context.Context, key string, value []byte) error { + stmt := fmt.Sprintf( + `INSERT INTO resource_events (key_path, value) VALUES (%s, %s)`, + k.dialect.ArgPlaceholder(1), k.dialect.ArgPlaceholder(2), + ) + + _, err := k.db.ExecContext(ctx, stmt, eventsSection+"/"+key, value) + return err +} + func (k *sqlKV) Save(ctx context.Context, section string, key string) (io.WriteCloser, error) { panic("not implemented!") } diff --git a/pkg/storage/unified/testing/kv.go b/pkg/storage/unified/testing/kv.go index f30f1d761d7..9688c5c1029 100644 --- a/pkg/storage/unified/testing/kv.go +++ b/pkg/storage/unified/testing/kv.go @@ -86,15 +86,20 @@ func RunKVTest(t *testing.T, newKV NewKVFunc, opts *KVTestOptions) { func runTestKVGet(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) - section := nsPrefix + "-get" + // Use `eventsSection` as the section for these tests, as the sqlkv implementation + // needs a real section to determine which table to use, and apply `nsPrefix` on + // the key itself. + section := "unified/events" + keyPrefix := nsPrefix + "-get" + prefixed := func(name string) string { return keyPrefix + "/" + name } t.Run("get existing key", func(t *testing.T) { // First save a key testValue := "test value for get" - saveKVHelper(t, kv, ctx, section, "existing-key", strings.NewReader(testValue)) + saveKVHelper(t, kv, ctx, section, prefixed("existing-key"), strings.NewReader(testValue)) // Now get it - reader, err := kv.Get(ctx, section, "existing-key") + reader, err := kv.Get(ctx, section, prefixed("existing-key")) require.NoError(t, err) // Read the value @@ -108,16 +113,22 @@ func runTestKVGet(t *testing.T, kv resource.KV, nsPrefix string) { }) t.Run("get non-existent key", func(t *testing.T) { - _, err := kv.Get(ctx, section, "non-existent-key") + _, err := kv.Get(ctx, section, prefixed("non-existent-key")) assert.Error(t, err) assert.Equal(t, resource.ErrNotFound, err) }) t.Run("get with empty section", func(t *testing.T) { - _, err := kv.Get(ctx, "", "some-key") + _, err := kv.Get(ctx, "", prefixed("some-key")) assert.Error(t, err) assert.Contains(t, err.Error(), "section is required") }) + + t.Run("get with empty key", func(t *testing.T) { + _, err := kv.Get(ctx, section, "") + assert.Error(t, err) + assert.Contains(t, err.Error(), "key is required") + }) } func runTestKVSave(t *testing.T, kv resource.KV, nsPrefix string) { @@ -800,6 +811,19 @@ func runTestKVBatchDelete(t *testing.T, kv resource.KV, nsPrefix string) { // saveKVHelper is a helper function to save data to KV store using the new WriteCloser interface func saveKVHelper(t *testing.T, kv resource.KV, ctx context.Context, section, key string, value io.Reader) { t.Helper() + + // TODO: remove this check once the sqlkv implementation supports `Save`. + type testingSaver interface { + TestingSave(context.Context, string, []byte) error + } + + if saver, ok := kv.(testingSaver); ok { + blob, err := io.ReadAll(value) + require.NoError(t, err) + require.NoError(t, saver.TestingSave(ctx, key, blob)) + return + } + writer, err := kv.Save(ctx, section, key) require.NoError(t, err) _, err = io.Copy(writer, value) diff --git a/pkg/storage/unified/testing/kv_test.go b/pkg/storage/unified/testing/kv_test.go index 4dbd27d5ec9..4fc343d450e 100644 --- a/pkg/storage/unified/testing/kv_test.go +++ b/pkg/storage/unified/testing/kv_test.go @@ -47,7 +47,6 @@ func TestSQLKV(t *testing.T) { }, &KVTestOptions{ NSPrefix: "sql-kv-test", SkipTests: map[string]bool{ - TestKVGet: true, TestKVSave: true, TestKVDelete: true, TestKVKeys: true, From 30fb1c032a93c45c1d36d29ec520481c846c4011 Mon Sep 17 00:00:00 2001 From: Luminessa Starlight Date: Tue, 16 Dec 2025 15:01:53 -0500 Subject: [PATCH 166/297] Rudderstack: Update init logic to automatically use new url when new config is set (#115275) update rudderstack init logic to rely on existence of old or new sdk config option being set and only defer to feature toggle if both or neither is set --- public/app/core/services/echo/init.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/public/app/core/services/echo/init.ts b/public/app/core/services/echo/init.ts index d4ff585b700..5e49e72422a 100644 --- a/public/app/core/services/echo/init.ts +++ b/public/app/core/services/echo/init.ts @@ -146,7 +146,19 @@ async function initRudderstackBackend() { return; } - const modulePromise = config.featureToggles.rudderstackUpgrade + // this will need to be updated when rudderstackSdkV3Url is added + // Desired logic: if only one of the sdk urls is provided, use respective code + // otherwise defer to the feature toggle. + const fakeConfigRudderstackSdkV3Url: string | undefined = undefined; + + const hasOldSdkUrl = Boolean(config.rudderstackSdkUrl); + const hasNewSdkUrl = Boolean(fakeConfigRudderstackSdkV3Url); + const onlyOneConfigURLSet = hasOldSdkUrl !== hasNewSdkUrl; + const useNewRudderstack = onlyOneConfigURLSet ? hasNewSdkUrl : config.featureToggles.rudderstackUpgrade; + + const configUrl = useNewRudderstack ? fakeConfigRudderstackSdkV3Url : config.rudderstackSdkUrl; + + const modulePromise = useNewRudderstack ? import('./backends/analytics/RudderstackV3Backend') : import('./backends/analytics/RudderstackBackend'); @@ -157,7 +169,7 @@ async function initRudderstackBackend() { dataPlaneUrl: config.rudderstackDataPlaneUrl, user: contextSrv.user, sdkUrl: config.rudderstackSdkUrl, - configUrl: config.rudderstackConfigUrl, + configUrl: configUrl, integrationsUrl: config.rudderstackIntegrationsUrl, buildInfo: config.buildInfo, }) From f2c30cbbd10cdf5221d2ded07db10121555f2351 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 16 Dec 2025 15:56:02 -0500 Subject: [PATCH 167/297] Alerting: Protected fields for Contact points (#115442) * Alerting: Protect sensitive fields of contact points from unauthorized modification - Introduce a new permission alert.notifications.receivers.protected:write. The permission is granted to contact point administrators. - Introduce field Protected to NotifierOption - Introduce DiffReport for models.Integrations with focus on Settings. The diff report is extended with methods that return all keys that are different between two settings. - Add new annotation 'grafana.com/access/CanModifyProtected' to Receiver model - Update receiver service to enforce the permission and return status 403 if unauthorized user modifies protected field - Update receiver testing API to enforce permission and return status 403 if unauthorized user modifies protected field. - Update UI to disable protected fields if user cannot modify them --- eslint-suppressions.json | 27 -- pkg/api/alerting.go | 64 ++- .../notifications/receiver/conversions.go | 9 +- pkg/services/accesscontrol/models.go | 1 + .../ossaccesscontrol/receivers.go | 2 +- pkg/services/ngalert/accesscontrol.go | 3 +- .../ngalert/accesscontrol/receivers.go | 64 ++- .../ngalert/accesscontrol/receivers_test.go | 27 ++ pkg/services/ngalert/api/api_alertmanager.go | 10 +- pkg/services/ngalert/models/permissions.go | 10 +- pkg/services/ngalert/models/receivers_diff.go | 230 +++++++++ .../ngalert/models/receivers_diff_test.go | 262 ++++++++++ pkg/services/ngalert/models/testing.go | 6 + .../ngalert/notifier/alertmanager_config.go | 2 +- pkg/services/ngalert/notifier/crypto.go | 43 +- pkg/services/ngalert/notifier/receiver_svc.go | 15 + .../ngalert/notifier/receiver_svc_err.go | 30 ++ .../ngalert/notifier/receiver_svc_test.go | 55 ++- .../migrations/accesscontrol/alerting.go | 46 ++ .../sqlstore/migrations/migrations.go | 2 + .../alert-notifiers-v1-snapshot.json | 20 + .../notifications/receivers/receiver_test.go | 104 ++-- .../test-data/imported-expected-snapshot.json | 16 + .../receivers/NewReceiverView.test.tsx | 4 +- .../receivers/form/ChannelOptions.tsx | 23 +- .../receivers/form/ChannelSubForm.test.tsx | 1 + .../receivers/form/ChannelSubForm.tsx | 5 + .../receivers/form/CloudReceiverForm.tsx | 1 + .../receivers/form/GrafanaReceiverForm.tsx | 5 +- .../receivers/form/ReceiverForm.tsx | 14 +- .../form/fields/OptionField.test.tsx | 448 ++++++++++++++++++ .../receivers/form/fields/OptionField.tsx | 28 +- .../alerting/unified/types/alerting.ts | 6 + .../alerting/unified/utils/k8s/constants.ts | 2 + .../alerting/unified/utils/k8s/utils.ts | 3 + public/app/types/accessControl.ts | 1 + public/locales/en-US/grafana.json | 7 + 37 files changed, 1482 insertions(+), 114 deletions(-) create mode 100644 pkg/services/ngalert/models/receivers_diff.go create mode 100644 pkg/services/ngalert/models/receivers_diff_test.go create mode 100644 pkg/services/ngalert/notifier/receiver_svc_err.go create mode 100644 public/app/features/alerting/unified/components/receivers/form/fields/OptionField.test.tsx diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 2e7b5712911..61bc82f1a7b 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1432,22 +1432,6 @@ "count": 1 } }, - "public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx": { - "@typescript-eslint/consistent-type-assertions": { - "count": 1 - }, - "@typescript-eslint/no-explicit-any": { - "count": 2 - }, - "no-restricted-syntax": { - "count": 1 - } - }, - "public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx": { - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/alerting/unified/components/receivers/form/CloudCommonChannelSettings.tsx": { "no-restricted-syntax": { "count": 1 @@ -1458,17 +1442,6 @@ "count": 1 } }, - "public/app/features/alerting/unified/components/receivers/form/ReceiverForm.tsx": { - "@typescript-eslint/consistent-type-assertions": { - "count": 2 - }, - "@typescript-eslint/no-explicit-any": { - "count": 1 - }, - "no-restricted-syntax": { - "count": 1 - } - }, "public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 27abb6ebbfc..8fb2f366bf2 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/api/response" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + "github.com/grafana/grafana/pkg/services/ngalert/models" ) func (hs *HTTPServer) GetAlertNotifiers() func(*contextmodel.ReqContext) response.Response { @@ -23,13 +24,13 @@ func (hs *HTTPServer) GetAlertNotifiers() func(*contextmodel.ReqContext) respons } type NotifierPlugin struct { - Type string `json:"type"` - TypeAlias string `json:"typeAlias,omitempty"` - Name string `json:"name"` - Heading string `json:"heading"` - Description string `json:"description"` - Info string `json:"info"` - Options []schema.Field `json:"options"` + Type string `json:"type"` + TypeAlias string `json:"typeAlias,omitempty"` + Name string `json:"name"` + Heading string `json:"heading"` + Description string `json:"description"` + Info string `json:"info"` + Options []Field `json:"options"` } result := make([]*NotifierPlugin, 0, len(v2)) @@ -44,9 +45,56 @@ func (hs *HTTPServer) GetAlertNotifiers() func(*contextmodel.ReqContext) respons Description: s.Description, Heading: s.Heading, Info: s.Info, - Options: v1.Options, + Options: schemaFieldsToFields(s.Type, nil, v1.Options), }) } return response.JSON(http.StatusOK, result) } } + +type Field struct { + Element schema.ElementType `json:"element"` + InputType schema.InputType `json:"inputType"` + Label string `json:"label"` + Description string `json:"description"` + Placeholder string `json:"placeholder"` + PropertyName string `json:"propertyName"` + SelectOptions []schema.SelectOption `json:"selectOptions"` + ShowWhen schema.ShowWhen `json:"showWhen"` + Required bool `json:"required"` + Protected bool `json:"protected,omitempty"` + ValidationRule string `json:"validationRule"` + Secure bool `json:"secure"` + DependsOn string `json:"dependsOn"` + SubformOptions []Field `json:"subformOptions"` +} + +func schemaFieldsToFields(iType schema.IntegrationType, parent schema.IntegrationFieldPath, fields []schema.Field) []Field { + if fields == nil { + return nil + } + result := make([]Field, 0, len(fields)) + for _, f := range fields { + result = append(result, schemaFieldToField(iType, parent, f)) + } + return result +} + +func schemaFieldToField(iType schema.IntegrationType, parent schema.IntegrationFieldPath, f schema.Field) Field { + return Field{ + Element: f.Element, + InputType: f.InputType, + Label: f.Label, + Description: f.Description, + Placeholder: f.Placeholder, + PropertyName: f.PropertyName, + SelectOptions: f.SelectOptions, + ShowWhen: f.ShowWhen, + Required: f.Required, + ValidationRule: f.ValidationRule, + Secure: f.Secure, + DependsOn: f.DependsOn, + SubformOptions: schemaFieldsToFields(iType, append(parent, f.PropertyName), f.SubformOptions), + Protected: models.IsProtectedField(iType, append(parent, f.PropertyName)), + } +} diff --git a/pkg/registry/apps/alerting/notifications/receiver/conversions.go b/pkg/registry/apps/alerting/notifications/receiver/conversions.go index b0d79121912..993c63a46fd 100644 --- a/pkg/registry/apps/alerting/notifications/receiver/conversions.go +++ b/pkg/registry/apps/alerting/notifications/receiver/conversions.go @@ -110,10 +110,11 @@ func convertToK8sResource( } var permissionMapper = map[ngmodels.ReceiverPermission]string{ - ngmodels.ReceiverPermissionReadSecret: "canReadSecrets", - ngmodels.ReceiverPermissionAdmin: "canAdmin", - ngmodels.ReceiverPermissionWrite: "canWrite", - ngmodels.ReceiverPermissionDelete: "canDelete", + ngmodels.ReceiverPermissionReadSecret: "canReadSecrets", + ngmodels.ReceiverPermissionAdmin: "canAdmin", + ngmodels.ReceiverPermissionWrite: "canWrite", + ngmodels.ReceiverPermissionDelete: "canDelete", + ngmodels.ReceiverPermissionModifyProtected: "canModifyProtected", } func convertToDomainModel(receiver *model.Receiver) (*ngmodels.Receiver, map[string][]string, error) { diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go index ec67cd03c64..b18fb4134f3 100644 --- a/pkg/services/accesscontrol/models.go +++ b/pkg/services/accesscontrol/models.go @@ -460,6 +460,7 @@ const ( ActionAlertingReceiversReadSecrets = "alert.notifications.receivers.secrets:read" ActionAlertingReceiversCreate = "alert.notifications.receivers:create" ActionAlertingReceiversUpdate = "alert.notifications.receivers:write" + ActionAlertingReceiversUpdateProtected = "alert.notifications.receivers.protected:write" ActionAlertingReceiversDelete = "alert.notifications.receivers:delete" ActionAlertingReceiversTest = "alert.notifications.receivers:test" ActionAlertingReceiversPermissionsRead = "receivers.permissions:read" diff --git a/pkg/services/accesscontrol/ossaccesscontrol/receivers.go b/pkg/services/accesscontrol/ossaccesscontrol/receivers.go index 49ca4a0b3e8..9dd4def1db3 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/receivers.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/receivers.go @@ -23,7 +23,7 @@ import ( var ReceiversViewActions = []string{accesscontrol.ActionAlertingReceiversRead} var ReceiversEditActions = append(ReceiversViewActions, []string{accesscontrol.ActionAlertingReceiversUpdate, accesscontrol.ActionAlertingReceiversDelete}...) -var ReceiversAdminActions = append(ReceiversEditActions, []string{accesscontrol.ActionAlertingReceiversReadSecrets, accesscontrol.ActionAlertingReceiversPermissionsRead, accesscontrol.ActionAlertingReceiversPermissionsWrite}...) +var ReceiversAdminActions = append(ReceiversEditActions, []string{accesscontrol.ActionAlertingReceiversReadSecrets, accesscontrol.ActionAlertingReceiversPermissionsRead, accesscontrol.ActionAlertingReceiversPermissionsWrite, accesscontrol.ActionAlertingReceiversUpdateProtected}...) // defaultPermissions returns the default permissions for a newly created receiver. func defaultPermissions() []accesscontrol.SetResourcePermissionCommand { diff --git a/pkg/services/ngalert/accesscontrol.go b/pkg/services/ngalert/accesscontrol.go index 15e3212de03..6754b3c0680 100644 --- a/pkg/services/ngalert/accesscontrol.go +++ b/pkg/services/ngalert/accesscontrol.go @@ -289,12 +289,13 @@ var ( Role: accesscontrol.RoleDTO{ Name: accesscontrol.FixedRolePrefix + "alerting:admin", DisplayName: "Full admin access", - Description: "Full write access in Grafana and all external providers, including their permissions and secrets", + Description: "Full write access in Grafana and all external providers, including their permissions, protected fields and secrets", Group: models.AlertRolesGroup, Permissions: accesscontrol.ConcatPermissions(alertingWriterRole.Role.Permissions, []accesscontrol.Permission{ {Action: accesscontrol.ActionAlertingReceiversPermissionsRead, Scope: models.ScopeReceiversAll}, {Action: accesscontrol.ActionAlertingReceiversPermissionsWrite, Scope: models.ScopeReceiversAll}, {Action: accesscontrol.ActionAlertingReceiversReadSecrets, Scope: models.ScopeReceiversAll}, + {Action: accesscontrol.ActionAlertingReceiversUpdateProtected, Scope: models.ScopeReceiversAll}, }), }, Grants: []string{string(org.RoleAdmin)}, diff --git a/pkg/services/ngalert/accesscontrol/receivers.go b/pkg/services/ngalert/accesscontrol/receivers.go index eb2904854bf..f12a9114912 100644 --- a/pkg/services/ngalert/accesscontrol/receivers.go +++ b/pkg/services/ngalert/accesscontrol/receivers.go @@ -95,6 +95,26 @@ var ( ) } + // Asserts pre-conditions for access to modify protected fields of receivers. If this evaluates to false, the user cannot modify protected fields of any receivers. + updateReceiversProtectedPreConditionsEval = ac.EvalAll( + updateReceiversPreConditionsEval, + ac.EvalPermission(ac.ActionAlertingReceiversUpdateProtected), // Action for receivers. UID scope. + ) + + // Asserts access to modify protected fields of a specific receiver. + updateReceiverProtectedEval = func(uid string) ac.Evaluator { + return ac.EvalAll( + updateReceiverEval(uid), + ac.EvalPermission(ac.ActionAlertingReceiversUpdateProtected, models.ScopeReceiversProvider.GetResourceScopeUID(uid)), + ) + } + + // Asserts access to modify protected fields of all receivers. + updateAllReceiverProtectedEval = ac.EvalAll( + updateAllReceiversEval, + ac.EvalPermission(ac.ActionAlertingReceiversUpdateProtected, models.ScopeReceiversAll), + ) + // Delete // Asserts pre-conditions for delete access to receivers. If this evaluates to false, the user cannot delete any receivers. @@ -141,12 +161,13 @@ var ( ) type ReceiverAccess[T models.Identified] struct { - read actionAccess[T] - readDecrypted actionAccess[T] - create actionAccess[T] - update actionAccess[T] - delete actionAccess[T] - permissions actionAccess[T] + read actionAccess[T] + readDecrypted actionAccess[T] + create actionAccess[T] + update actionAccess[T] + updateProtected actionAccess[T] + delete actionAccess[T] + permissions actionAccess[T] } // NewReceiverAccess creates a new ReceiverAccess service. If includeProvisioningActions is true, the service will include @@ -201,6 +222,18 @@ func NewReceiverAccess[T models.Identified](a ac.AccessControl, includeProvision }, authorizeAll: updateAllReceiversEval, }, + updateProtected: actionAccess[T]{ + genericService: genericService{ + ac: a, + }, + resource: "receiver", + action: "update protected fields of", // this produces message "user is not authorized to update protected fields of X receiver" + authorizeSome: updateReceiversProtectedPreConditionsEval, + authorizeOne: func(receiver models.Identified) ac.Evaluator { + return updateReceiverProtectedEval(receiver.GetUID()) + }, + authorizeAll: updateAllReceiverProtectedEval, + }, delete: actionAccess[T]{ genericService: genericService{ ac: a, @@ -311,6 +344,14 @@ func (s ReceiverAccess[T]) AuthorizeUpdate(ctx context.Context, user identity.Re return s.update.Authorize(ctx, user, receiver) } +func (s ReceiverAccess[T]) HasUpdateProtected(ctx context.Context, user identity.Requester, receiver T) (bool, error) { + return s.updateProtected.Has(ctx, user, receiver) +} + +func (s ReceiverAccess[T]) AuthorizeUpdateProtected(ctx context.Context, user identity.Requester, receiver T) error { + return s.updateProtected.Authorize(ctx, user, receiver) +} + // Global // AuthorizeCreate checks if user has access to create receivers. Returns an error if user does not have access. @@ -380,6 +421,12 @@ func (s ReceiverAccess[T]) Access(ctx context.Context, user identity.Requester, basePerms.Set(models.ReceiverPermissionDelete, true) // Has access to all receivers. } + if err := s.updateProtected.AuthorizePreConditions(ctx, user); err != nil { + basePerms.Set(models.ReceiverPermissionModifyProtected, false) + } else if err := s.updateProtected.AuthorizeAll(ctx, user); err == nil { + basePerms.Set(models.ReceiverPermissionModifyProtected, true) + } + if basePerms.AllSet() { // Shortcut for the case when all permissions are known based on preconditions. result := make(map[string]models.ReceiverPermissionSet, len(receivers)) @@ -412,6 +459,11 @@ func (s ReceiverAccess[T]) Access(ctx context.Context, user identity.Requester, permSet.Set(models.ReceiverPermissionDelete, err == nil) } + if _, ok := permSet.Has(models.ReceiverPermissionModifyProtected); !ok { + err := s.updateProtected.authorize(ctx, user, rcv) + permSet.Set(models.ReceiverPermissionModifyProtected, err == nil) + } + result[rcv.GetUID()] = permSet } return result, nil diff --git a/pkg/services/ngalert/accesscontrol/receivers_test.go b/pkg/services/ngalert/accesscontrol/receivers_test.go index 7be1e9498e2..316fa3a98a6 100644 --- a/pkg/services/ngalert/accesscontrol/receivers_test.go +++ b/pkg/services/ngalert/accesscontrol/receivers_test.go @@ -204,6 +204,33 @@ func TestReceiverAccess(t *testing.T) { recv3.UID: permissions(), }, }, + { + name: "update protected cannot update receivers", + user: newEmptyUser( + ac.Permission{Action: ac.ActionAlertingReceiversRead, Scope: models.ScopeReceiversAll}, + ac.Permission{Action: ac.ActionAlertingReceiversUpdateProtected, Scope: models.ScopeReceiversAll}, + ), + expected: map[string]models.ReceiverPermissionSet{ + recv1.UID: permissions(), + recv2.UID: permissions(), + recv3.UID: permissions(), + }, + }, + { + name: "update protected receivers", + user: newEmptyUser( + ac.Permission{Action: ac.ActionAlertingReceiversRead, Scope: models.ScopeReceiversAll}, + ac.Permission{Action: ac.ActionAlertingReceiversUpdateProtected, Scope: models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID)}, + ac.Permission{Action: ac.ActionAlertingReceiversUpdate, Scope: models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID)}, + ac.Permission{Action: ac.ActionAlertingReceiversUpdate, Scope: models.ScopeReceiversProvider.GetResourceScopeUID(recv2.UID)}, + ac.Permission{Action: ac.ActionAlertingReceiversUpdateProtected, Scope: models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID)}, + ), + expected: map[string]models.ReceiverPermissionSet{ + recv1.UID: permissions(models.ReceiverPermissionWrite, models.ReceiverPermissionModifyProtected), + recv2.UID: permissions(models.ReceiverPermissionWrite), + recv3.UID: permissions(), + }, + }, // Receiver delete. { name: "global receiver delete should have delete but no write", diff --git a/pkg/services/ngalert/api/api_alertmanager.go b/pkg/services/ngalert/api/api_alertmanager.go index 90dcb26fc74..f8b7216bcdb 100644 --- a/pkg/services/ngalert/api/api_alertmanager.go +++ b/pkg/services/ngalert/api/api_alertmanager.go @@ -10,8 +10,10 @@ import ( "time" alertingNotify "github.com/grafana/alerting/notify" + "github.com/grafana/alerting/receivers/schema" "github.com/grafana/grafana/pkg/api/response" + "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -32,6 +34,7 @@ const ( type receiversAuthz interface { FilterRead(ctx context.Context, user identity.Requester, receivers ...ReceiverStatus) ([]ReceiverStatus, error) + AuthorizeUpdateProtected(context.Context, identity.Requester, ReceiverStatus) error } type AlertmanagerSrv struct { @@ -210,11 +213,16 @@ func (srv AlertmanagerSrv) RouteGetReceivers(c *contextmodel.ReqContext) respons } func (srv AlertmanagerSrv) RoutePostTestReceivers(c *contextmodel.ReqContext, body apimodels.TestReceiversConfigBodyParams) response.Response { - if err := srv.crypto.ProcessSecureSettings(c.Req.Context(), c.GetOrgID(), body.Receivers); err != nil { + if err := srv.crypto.ProcessSecureSettings(c.Req.Context(), c.GetOrgID(), body.Receivers, func(receiverName string, paths []schema.IntegrationFieldPath) error { + return srv.receiverAuthz.AuthorizeUpdateProtected(c.Req.Context(), c.SignedInUser, ReceiverStatus{Name: receiverName}) + }); err != nil { var unknownReceiverError UnknownReceiverError if errors.As(err, &unknownReceiverError) { return ErrResp(http.StatusBadRequest, err, "") } + if errors.As(err, &errutil.Error{}) { + return response.Err(err) + } return ErrResp(http.StatusInternalServerError, err, "failed to post process Alertmanager configuration") } diff --git a/pkg/services/ngalert/models/permissions.go b/pkg/services/ngalert/models/permissions.go index a34e184fc99..ce52755eac8 100644 --- a/pkg/services/ngalert/models/permissions.go +++ b/pkg/services/ngalert/models/permissions.go @@ -9,10 +9,11 @@ import ( type ReceiverPermission string const ( - ReceiverPermissionReadSecret ReceiverPermission = "secrets" - ReceiverPermissionAdmin ReceiverPermission = "admin" - ReceiverPermissionWrite ReceiverPermission = "write" - ReceiverPermissionDelete ReceiverPermission = "delete" + ReceiverPermissionReadSecret ReceiverPermission = "secrets" + ReceiverPermissionAdmin ReceiverPermission = "admin" + ReceiverPermissionWrite ReceiverPermission = "write" + ReceiverPermissionDelete ReceiverPermission = "delete" + ReceiverPermissionModifyProtected ReceiverPermission = "modify-protected" ) // ReceiverPermissions returns all possible silence permissions. @@ -22,6 +23,7 @@ func ReceiverPermissions() []ReceiverPermission { ReceiverPermissionAdmin, ReceiverPermissionWrite, ReceiverPermissionDelete, + ReceiverPermissionModifyProtected, } } diff --git a/pkg/services/ngalert/models/receivers_diff.go b/pkg/services/ngalert/models/receivers_diff.go new file mode 100644 index 00000000000..bfe9328542f --- /dev/null +++ b/pkg/services/ngalert/models/receivers_diff.go @@ -0,0 +1,230 @@ +package models + +import ( + "fmt" + "reflect" + "strings" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/grafana/alerting/receivers/schema" + + "github.com/grafana/grafana/pkg/util/cmputil" +) + +type IntegrationDiffReport struct { + cmputil.DiffReport +} + +// expandPaths recursively collects all sub-paths for keys in the provided map value +func (r IntegrationDiffReport) expandPaths(basePath schema.IntegrationFieldPath, mapVal reflect.Value) []schema.IntegrationFieldPath { + result := make([]schema.IntegrationFieldPath, 0) + iter := mapVal.MapRange() + for iter.Next() { + keyStr := fmt.Sprintf("%v", iter.Key()) // Assume string keys + p := basePath.With(keyStr) + // Recurse if the sub-value is another map + if m, ok := r.getMap(iter.Value()); ok { + result = append(result, r.expandPaths(p, m)...) + continue + } + result = append(result, p) + } + return result +} + +func (r IntegrationDiffReport) getMap(v reflect.Value) (reflect.Value, bool) { + if v.Kind() == reflect.Map { + return v, true + } + if v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { + return r.getMap(v.Elem()) + } + return reflect.Value{}, false +} + +func (r IntegrationDiffReport) needExpand(diff cmputil.Diff) (reflect.Value, bool) { + ml, lok := r.getMap(diff.Left) + mr, rok := r.getMap(diff.Right) + if lok == rok { + return reflect.Value{}, false + } + if lok { + return ml, true + } + return mr, true +} + +func (r IntegrationDiffReport) GetSettingsPaths() []schema.IntegrationFieldPath { + diffs := r.GetDiffsForField("Settings") + paths := make([]schema.IntegrationFieldPath, 0, len(diffs)) + for _, diff := range diffs { + // diff.Path has format like Settings[url] or Settings[sub-form][field] + p := diff.Path + var path schema.IntegrationFieldPath + for { + start := strings.Index(p, "[") + if start == -1 { + break + } + p = p[start+1:] + end := strings.Index(p, "]") + if end == -1 { + break + } + fieldName := p[:end] + p = p[end+1:] + path = append(path, fieldName) + } + if m, ok := r.needExpand(diff); ok { + paths = append(paths, r.expandPaths(path, m)...) + continue + } + if len(path) > 0 { + paths = append(paths, path) + } + } + return paths +} + +func (r IntegrationDiffReport) GetSecureSettingsPaths() []schema.IntegrationFieldPath { + diffs := r.GetDiffsForField("SecureSettings") + paths := make([]schema.IntegrationFieldPath, 0, len(diffs)) + for _, diff := range diffs { + if diff.Path == "SecureSettings" { + if m, ok := r.needExpand(diff); ok { + paths = append(paths, r.expandPaths(nil, m)...) + } + continue + } + // diff.Path has format like SecureSettings[field.sub-field.sub] + p := schema.ParseIntegrationPath(diff.Path[len("SecureSettings[") : len(diff.Path)-1]) + paths = append(paths, p) + } + return paths +} + +func (integration *Integration) Diff(incoming Integration) IntegrationDiffReport { + var reporter cmputil.DiffReporter + var settingsCmp = cmpopts.AcyclicTransformer("settingsMap", func(in map[string]any) map[string]any { + if in == nil { + return map[string]any{} + } + return in + }) + var secureCmp = cmpopts.AcyclicTransformer("secureMap", func(in map[string]string) map[string]string { + if in == nil { + return map[string]string{} + } + return in + }) + schemaCmp := cmp.Comparer(func(a, b schema.IntegrationSchemaVersion) bool { + isAZero := reflect.ValueOf(a).IsZero() + isBZero := reflect.ValueOf(b).IsZero() + if isAZero && isBZero { + return true + } + if isAZero || isBZero { + return false + } + return a.Type() == b.Type() && a.Version == b.Version + }) + var cur Integration + if integration != nil { + cur = *integration + } + cmp.Equal(cur, incoming, cmp.Reporter(&reporter), settingsCmp, secureCmp, schemaCmp) + return IntegrationDiffReport{DiffReport: reporter.Diffs} +} + +// HasReceiversDifferentProtectedFields returns true if the receiver has any protected fields that are different from the incoming receiver. +func HasReceiversDifferentProtectedFields(existing, incoming *Receiver) map[string][]schema.IntegrationFieldPath { + existingIntegrations := make(map[string]*Integration, len(existing.Integrations)) + for _, integration := range existing.Integrations { + existingIntegrations[integration.UID] = integration + } + + var result = make(map[string][]schema.IntegrationFieldPath) + for _, in := range incoming.Integrations { + if in.UID == "" { + continue + } + ex, ok := existingIntegrations[in.UID] + if !ok { + continue + } + paths := HasIntegrationsDifferentProtectedFields(ex, in) + if len(paths) > 0 { + result[in.UID] = paths + } + } + return result +} + +// HasIntegrationsDifferentProtectedFields returns list of paths to protected fields that are different between two integrations. +func HasIntegrationsDifferentProtectedFields(existing, incoming *Integration) []schema.IntegrationFieldPath { + diff := existing.Diff(*incoming) + // The incoming receiver always has both secret and non-secret fields in Settings. + // So, if it's specified and happens to be sensitive, we consider it changed + var result []schema.IntegrationFieldPath + settingsDiff := diff.GetSettingsPaths() + for _, path := range settingsDiff { + if IsProtectedField(incoming.Config.Type(), path) { + result = append(result, path) + } + } + return result +} + +// IsProtectedField returns true if the field at the given path is existing protected one. +// This includes: +// 1. URL fields marked as secure in the schema (e.g., webhook URLs with credentials) +// 2. URL fields NOT marked as secure but could contain credentials (e.g., API endpoints) +func IsProtectedField(integrationType schema.IntegrationType, path schema.IntegrationFieldPath) bool { + str := strings.ToLower(string(integrationType)) + pathStr := path.String() + + switch str { + case "prometheus-alertmanager": + return pathStr == "url" + case "dingding": + return pathStr == "url" // marked as secure + case "discord": + return pathStr == "url" // marked as secure (webhook URL) + case "googlechat": + return pathStr == "url" // marked as secure + case "jira": + return pathStr == "api_url" + case "kafka": + return pathStr == "kafkaRestProxy" + case "line": + return false + case "mqtt": + return pathStr == "brokerUrl" + case "oncall": + return pathStr == "url" + case "opsgenie": + return pathStr == "apiUrl" + case "pagerduty": + return pathStr == "url" + case "sensugo": + return pathStr == "url" + case "slack": + return pathStr == "url" || pathStr == "endpointUrl" + case "teams": + return pathStr == "url" + case "victorops": + return pathStr == "url" // marked as secure + case "webex": + return pathStr == "api_url" + case "webhook": + return pathStr == "url" || + pathStr == "http_config.oauth2.token_url" || + pathStr == "http_config.oauth2.proxy_config.proxy_url" + case "wecom": + return pathStr == "url" || // marked as secure + pathStr == "endpointUrl" + default: + return false + } +} diff --git a/pkg/services/ngalert/models/receivers_diff_test.go b/pkg/services/ngalert/models/receivers_diff_test.go new file mode 100644 index 00000000000..dffeaee1dc2 --- /dev/null +++ b/pkg/services/ngalert/models/receivers_diff_test.go @@ -0,0 +1,262 @@ +package models + +import ( + "slices" + "testing" + + alertingNotify "github.com/grafana/alerting/notify" + "github.com/grafana/alerting/receivers/schema" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIntegrationDiff(t *testing.T) { + s, _ := alertingNotify.GetSchemaVersionForIntegration("webhook", schema.V1) + a := Integration{ + UID: "test-uid", + Name: "test-name", + Config: s, + DisableResolveMessage: false, + Settings: map[string]any{ + "url": "http://localhost", + "name": 123, + "flag": true, + "child": map[string]any{ + "sub-form-field": "test", + }, + }, + SecureSettings: map[string]string{ + "password": "12345", + "token": "token-12345", + }, + } + + t.Run("no diff if equal", func(t *testing.T) { + result := a.Diff(a) + assert.Empty(t, result) + }) + + t.Run("should deep compare settings", func(t *testing.T) { + b := a + b.Settings = map[string]any{ + "url": "http://localhost:123", + "flag": false, + "child": map[string]any{ + "sub-form-field": "test123", + "sub-child": map[string]any{ + "test": "test", + }, + }, + } + + result := a.Diff(b) + assert.ElementsMatch(t, + []string{"Settings[url]", "Settings[name]", "Settings[flag]", "Settings[child][sub-form-field]", "Settings[child][sub-child]"}, + result.Paths()) + }) + + t.Run("should shallow compare schemas", func(t *testing.T) { + b := a + b.Config, _ = alertingNotify.GetSchemaVersionForIntegration("slack", schema.V1) + result := a.Diff(b) + assert.ElementsMatch(t, + []string{"Config"}, + result.Paths()) + }) + + t.Run("should compare with zero objects", func(t *testing.T) { + result := a.Diff(Integration{}) + assert.ElementsMatch(t, + []string{ + "UID", + "Name", + "Config", + "Settings[child]", + "Settings[flag]", + "Settings[name]", + "Settings[url]", + "SecureSettings[password]", + "SecureSettings[token]", + }, + result.Paths()) + }) +} + +func TestIntegrationDiffReport_GetSettingsPaths(t *testing.T) { + a := Integration{ + UID: "test-uid", + Name: "test-name", + Config: schema.IntegrationSchemaVersion{}, + DisableResolveMessage: false, + Settings: map[string]any{ + "url": "http://localhost", + "child": map[string]any{ + "field": "test", + "sub-child": map[string]any{ + "test": "test", + }, + }, + }, + } + + testCases := []struct { + name string + left map[string]any + right map[string]any + paths []string + }{ + { + name: "empty", + left: map[string]any{}, + right: map[string]any{}, + }, + { + name: "left is empty", + left: map[string]any{}, + right: map[string]any{ + "field": "test", + }, + paths: []string{"field"}, + }, + { + name: "right is empty", + left: map[string]any{ + "field": "test", + }, + right: map[string]any{}, + paths: []string{"field"}, + }, + { + name: "expands nested", + left: map[string]any{ + "field": map[string]any{ + "sub-field": map[string]any{ + "test": "test", + }, + }, + }, + right: map[string]any{ + "another": map[string]any{ + "sub-field": map[string]any{ + "test": "test", + }, + }, + }, + paths: []string{ + "field.sub-field.test", + "another.sub-field.test", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + b := a + b.Settings = tc.right + a.Settings = tc.left + diff := a.Diff(b) + + actual := diff.GetSettingsPaths() + actualStrings := make([]string, 0, len(actual)) + for _, f := range actual { + actualStrings = append(actualStrings, f.String()) + } + assert.ElementsMatch(t, tc.paths, actualStrings) + }) + } +} + +func TestHasDifferentProtectedFields(t *testing.T) { + m := IntegrationMuts + + testCase := []struct { + name string + existing Integration + incoming Integration + expected map[string][]string + }{ + { + name: "different UID do not match", + existing: IntegrationGen(m.WithUID("existing"), m.WithValidConfig("webhook"))(), + incoming: IntegrationGen( + m.WithValidConfig("webhook"), + m.AddSetting("url", "http://some-other-url"), + m.WithUID("incoming"), + )(), + expected: nil, + }, + { + name: "find url protected", + existing: IntegrationGen(m.WithUID("1"), m.WithValidConfig("webhook"))(), + incoming: IntegrationGen( + m.WithValidConfig("webhook"), + m.AddSetting("url", "http://some-other-url"), + m.AddSetting("http_config", map[string]any{ + "oauth2": map[string]any{ + "proxy_config": map[string]any{ + "proxy_url": "http://some-other-url-proxy", + }, + "token_url": "http://some-other-url-token", + }, + }), + m.WithUID("1"), + )(), + expected: map[string][]string{ + "1": { + "http_config.oauth2.proxy_config.proxy_url", + "http_config.oauth2.token_url", + "url", + }, + }, + }, + { + name: "secure and protected", // simulate the situation when protected secured field is in secure settings but the incoming one has it in settings + existing: IntegrationGen( + m.WithUID("1"), + m.WithValidConfig("discord"), + m.RemoveSetting("url"), + m.WithSecureSettings(map[string]string{ + "url": "", + }))(), + incoming: IntegrationGen( + m.WithValidConfig("discord"), + m.AddSetting("url", "http://some-other-url"), + m.WithSecureSettings(nil), + m.WithUID("1"), + )(), + expected: map[string][]string{ + "1": { + "url", + }, + }, + }, + } + + for _, tc := range testCase { + t.Run(tc.name, func(t *testing.T) { + existing := &Receiver{ + Integrations: []*Integration{ + &tc.existing, + }, + } + incoming := &Receiver{ + Integrations: []*Integration{ + &tc.incoming, + }, + } + actual := HasReceiversDifferentProtectedFields(existing, incoming) + if len(tc.expected) == 0 { + require.Empty(t, actual) + return + } + actualStrings := make(map[string][]string, len(actual)) + for uid, paths := range actual { + for _, path := range paths { + actualStrings[uid] = append(actualStrings[uid], path.String()) + } + slices.Sort(actualStrings[uid]) + } + assert.EqualValues(t, tc.expected, actualStrings) + }) + } +} diff --git a/pkg/services/ngalert/models/testing.go b/pkg/services/ngalert/models/testing.go index 9542c35fce8..8180afb0012 100644 --- a/pkg/services/ngalert/models/testing.go +++ b/pkg/services/ngalert/models/testing.go @@ -1457,6 +1457,12 @@ func (n IntegrationMutators) AddSecureSetting(key, val string) Mutator[Integrati } } +func (n IntegrationMutators) RemoveSetting(key string) Mutator[Integration] { + return func(c *Integration) { + delete(c.Settings, key) + } +} + func randomMapKey[K comparable, V any](m map[K]V) (K, V) { randIdx := rand.Intn(len(m)) i := 0 diff --git a/pkg/services/ngalert/notifier/alertmanager_config.go b/pkg/services/ngalert/notifier/alertmanager_config.go index 87d573124e7..7e755903791 100644 --- a/pkg/services/ngalert/notifier/alertmanager_config.go +++ b/pkg/services/ngalert/notifier/alertmanager_config.go @@ -333,7 +333,7 @@ func (moa *MultiOrgAlertmanager) SaveAndApplyAlertmanagerConfiguration(ctx conte config.ExtraConfigs = extraConfigs } - if err := moa.Crypto.ProcessSecureSettings(ctx, org, config.AlertmanagerConfig.Receivers); err != nil { + if err := moa.Crypto.ProcessSecureSettings(ctx, org, config.AlertmanagerConfig.Receivers, nil); err != nil { return fmt.Errorf("failed to post process Alertmanager configuration: %w", err) } diff --git a/pkg/services/ngalert/notifier/crypto.go b/pkg/services/ngalert/notifier/crypto.go index ab5d5f03096..246cd03d64d 100644 --- a/pkg/services/ngalert/notifier/crypto.go +++ b/pkg/services/ngalert/notifier/crypto.go @@ -29,16 +29,18 @@ const ( cryptoPrefix = "crypto_" ) +type AuthorizeProtectedFn func(uid string, paths []schema.IntegrationFieldPath) error + // Crypto allows decryption of Alertmanager Configuration and encryption of arbitrary payloads. type Crypto interface { - LoadSecureSettings(ctx context.Context, orgId int64, receivers []*definitions.PostableApiReceiver) error + LoadSecureSettings(ctx context.Context, orgId int64, receivers []*definitions.PostableApiReceiver, fn AuthorizeProtectedFn) error Encrypt(ctx context.Context, payload []byte, opt secrets.EncryptionOptions) ([]byte, error) Decrypt(ctx context.Context, payload []byte) ([]byte, error) EncryptExtraConfigs(ctx context.Context, config *definitions.PostableUserConfig) error DecryptExtraConfigs(ctx context.Context, config *definitions.PostableUserConfig) error getDecryptedSecret(r *definitions.PostableGrafanaReceiver, key string) (string, error) - ProcessSecureSettings(ctx context.Context, orgId int64, recvs []*definitions.PostableApiReceiver) error + ProcessSecureSettings(ctx context.Context, orgId int64, recvs []*definitions.PostableApiReceiver, fn AuthorizeProtectedFn) error } // alertmanagerCrypto implements decryption of Alertmanager configuration and encryption of arbitrary payloads based on Grafana's encryptions. @@ -57,7 +59,7 @@ func NewCrypto(secrets secrets.Service, configs configurationStore, log log.Logg } // ProcessSecureSettings encrypts new secure settings and loads existing secure settings from the database. -func (c *alertmanagerCrypto) ProcessSecureSettings(ctx context.Context, orgId int64, recvs []*definitions.PostableApiReceiver) error { +func (c *alertmanagerCrypto) ProcessSecureSettings(ctx context.Context, orgId int64, recvs []*definitions.PostableApiReceiver, authorizeProtected AuthorizeProtectedFn) error { // First, we encrypt the new or updated secure settings. Then, we load the existing secure settings from the database // and add back any that weren't updated. // We perform these steps in this order to ensure the hash of the secure settings remains stable when no secure @@ -68,7 +70,7 @@ func (c *alertmanagerCrypto) ProcessSecureSettings(ctx context.Context, orgId in return fmt.Errorf("failed to encrypt receivers: %w", err) } - if err := c.LoadSecureSettings(ctx, orgId, recvs); err != nil { + if err := c.LoadSecureSettings(ctx, orgId, recvs, authorizeProtected); err != nil { return err } @@ -167,7 +169,7 @@ func encryptReceiverConfigs(c []*definitions.PostableApiReceiver, encrypt defini } // LoadSecureSettings adds the corresponding unencrypted secrets stored to the list of input receivers. -func (c *alertmanagerCrypto) LoadSecureSettings(ctx context.Context, orgId int64, receivers []*definitions.PostableApiReceiver) error { +func (c *alertmanagerCrypto) LoadSecureSettings(ctx context.Context, orgId int64, receivers []*definitions.PostableApiReceiver, authorizeProtected AuthorizeProtectedFn) error { // Get the last known working configuration. amConfig, err := c.configs.GetLatestAlertmanagerConfiguration(ctx, orgId) if err != nil { @@ -176,10 +178,10 @@ func (c *alertmanagerCrypto) LoadSecureSettings(ctx context.Context, orgId int64 return fmt.Errorf("failed to get latest configuration: %w", err) } } - + var currentConfig *definitions.PostableUserConfig currentReceiverMap := make(map[string]*definitions.PostableGrafanaReceiver) if amConfig != nil { - currentConfig, err := Load([]byte(amConfig.AlertmanagerConfiguration)) + currentConfig, err = Load([]byte(amConfig.AlertmanagerConfiguration)) // If the current config is un-loadable, treat it as if it never existed. Providing a new, valid config should be able to "fix" this state. if err != nil { c.log.Warn("Last known alertmanager configuration was invalid. Overwriting...") @@ -209,6 +211,33 @@ func (c *alertmanagerCrypto) LoadSecureSettings(ctx context.Context, orgId int64 return UnknownReceiverError{UID: gr.UID} } + if authorizeProtected != nil { + incoming, errIn := legacy_storage.PostableGrafanaReceiverToIntegration(gr) + existing, errEx := legacy_storage.PostableGrafanaReceiverToIntegration(cgmr) + var secure []schema.IntegrationFieldPath + authz := true + if errIn == nil && errEx == nil { + secure = models.HasIntegrationsDifferentProtectedFields(existing, incoming) + authz = len(secure) > 0 + } + // if conversion failed, consider there are changes and authorize + if authz && currentConfig != nil { + var receiverName string + NAME: + for _, rcv := range currentConfig.AlertmanagerConfig.Receivers { + for _, intg := range rcv.GrafanaManagedReceivers { + if intg.UID == cgmr.UID { + receiverName = rcv.Name + break NAME + } + } + } + if err := authorizeProtected(receiverName, secure); err != nil { + return err + } + } + } + // Frontend sends only the secure settings that have to be updated // Therefore we have to copy from the last configuration only those secure settings not included in the request for key, encryptedValue := range cgmr.SecureSettings { diff --git a/pkg/services/ngalert/notifier/receiver_svc.go b/pkg/services/ngalert/notifier/receiver_svc.go index 56a68fb3e55..e05b12a2920 100644 --- a/pkg/services/ngalert/notifier/receiver_svc.go +++ b/pkg/services/ngalert/notifier/receiver_svc.go @@ -80,6 +80,9 @@ type receiverAccessControlService interface { AuthorizeUpdate(context.Context, identity.Requester, *models.Receiver) error AuthorizeDeleteByUID(context.Context, identity.Requester, string) error + HasUpdateProtected(context.Context, identity.Requester, *models.Receiver) (bool, error) + AuthorizeUpdateProtected(context.Context, identity.Requester, *models.Receiver) error + Access(ctx context.Context, user identity.Requester, receivers ...*models.Receiver) (map[string]models.ReceiverPermissionSet, error) } @@ -474,6 +477,18 @@ func (rs *ReceiverService) UpdateReceiver(ctx context.Context, r *models.Receive return nil, err } + // if user does not have permissions to update protected, check the diff and return error if there is a change in protected fields + canUpdateProtected, _ := rs.authz.HasUpdateProtected(ctx, user, r) + if !canUpdateProtected { + diff := models.HasReceiversDifferentProtectedFields(existing, r) + if len(diff) > 0 { + err = rs.authz.AuthorizeUpdateProtected(ctx, user, r) + if err != nil { + return nil, makeProtectedFieldsAuthzError(err, diff) + } + } + } + // We need to perform two important steps to process settings on an updated integration: // 1. Encrypt new or updated secret fields as they will arrive in plain text. // 2. For updates, callers do not re-send unchanged secure settings and instead mark them in SecureFields. We need diff --git a/pkg/services/ngalert/notifier/receiver_svc_err.go b/pkg/services/ngalert/notifier/receiver_svc_err.go new file mode 100644 index 00000000000..06b27602f75 --- /dev/null +++ b/pkg/services/ngalert/notifier/receiver_svc_err.go @@ -0,0 +1,30 @@ +package notifier + +import ( + "errors" + "slices" + + "github.com/grafana/alerting/receivers/schema" + + "github.com/grafana/grafana/pkg/apimachinery/errutil" +) + +func makeProtectedFieldsAuthzError(err error, diff map[string][]schema.IntegrationFieldPath) error { + var authzErr errutil.Error + if !errors.As(err, &authzErr) { + return err + } + if authzErr.PublicPayload == nil { + authzErr.PublicPayload = map[string]interface{}{} + } + fields := make(map[string][]string, len(diff)) + for field, paths := range diff { + fields[field] = make([]string, len(paths)) + for i, path := range paths { + fields[field][i] = path.String() + } + slices.Sort(fields[field]) + } + authzErr.PublicPayload["changed_protected_fields"] = fields + return authzErr +} diff --git a/pkg/services/ngalert/notifier/receiver_svc_test.go b/pkg/services/ngalert/notifier/receiver_svc_test.go index 8d4b7630436..10e84f33b62 100644 --- a/pkg/services/ngalert/notifier/receiver_svc_test.go +++ b/pkg/services/ngalert/notifier/receiver_svc_test.go @@ -659,8 +659,9 @@ func TestReceiverService_Update(t *testing.T) { writer := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{ 1: { - accesscontrol.ActionAlertingNotificationsWrite: nil, - accesscontrol.ActionAlertingNotificationsRead: nil, + accesscontrol.ActionAlertingNotificationsWrite: nil, + accesscontrol.ActionAlertingNotificationsRead: nil, + accesscontrol.ActionAlertingReceiversUpdateProtected: {models.ScopeReceiversAll}, }, }} decryptUser := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{ @@ -1310,7 +1311,7 @@ func TestReceiverServiceAC_Update(t *testing.T) { }, }} - slackIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("slack")) + slackIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("webhook")) emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email")) recv1 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver1"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() recv2 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver2"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))() @@ -1322,8 +1323,8 @@ func TestReceiverServiceAC_Update(t *testing.T) { name string permissions map[string][]string existing []models.Receiver - - hasAccess []models.Receiver + incoming []models.Receiver + hasAccess []models.Receiver }{ { name: "not authorized without permissions", @@ -1411,6 +1412,43 @@ func TestReceiverServiceAC_Update(t *testing.T) { existing: allReceivers(), hasAccess: []models.Receiver{recv1, recv3}, }, + { + name: "protected fields modified without permission", + permissions: map[string][]string{ + accesscontrol.ActionAlertingReceiversUpdate: {models.ScopeReceiversAll}, + accesscontrol.ActionAlertingReceiversRead: {models.ScopeReceiversAll}, + }, + existing: []models.Receiver{ + recv1, + }, + incoming: []models.Receiver{ + func() models.Receiver { + f := recv1.Clone() + f.Integrations[0].Settings["url"] = "https://example.com/new" + return f + }(), + }, + hasAccess: nil, + }, + { + name: "protected fields modified with permission", + permissions: map[string][]string{ + accesscontrol.ActionAlertingReceiversUpdate: {models.ScopeReceiversAll}, + accesscontrol.ActionAlertingReceiversRead: {models.ScopeReceiversAll}, + accesscontrol.ActionAlertingReceiversUpdateProtected: {models.ScopeReceiversAll}, + }, + existing: []models.Receiver{ + recv1, + }, + incoming: []models.Receiver{ + func() models.Receiver { + f := recv1.Clone() + f.Integrations[0].Settings["url"] = "https://example.com/new" + return f + }(), + }, + hasAccess: []models.Receiver{recv1}, + }, } for _, tc := range testCases { @@ -1436,7 +1474,11 @@ func TestReceiverServiceAC_Update(t *testing.T) { } return false } - for _, recv := range allReceivers() { + incoming := allReceivers() + if tc.incoming != nil { + incoming = tc.incoming + } + for _, recv := range incoming { clone := recv.Clone() clone.Version = versions[recv.UID] response, err := sut.UpdateReceiver(context.Background(), &clone, nil, orgId, usr) @@ -1734,6 +1776,7 @@ func TestReceiverService_AccessControlMetadata(t *testing.T) { expectedPermissions.Set(models.ReceiverPermissionAdmin, false) expectedPermissions.Set(models.ReceiverPermissionWrite, false) expectedPermissions.Set(models.ReceiverPermissionDelete, false) + expectedPermissions.Set(models.ReceiverPermissionModifyProtected, false) expectedPermissions.Set(models.ReceiverPermissionReadSecret, true) expected := map[string]models.ReceiverPermissionSet{ diff --git a/pkg/services/sqlstore/migrations/accesscontrol/alerting.go b/pkg/services/sqlstore/migrations/accesscontrol/alerting.go index f9942fac80e..deddf59eb12 100644 --- a/pkg/services/sqlstore/migrations/accesscontrol/alerting.go +++ b/pkg/services/sqlstore/migrations/accesscontrol/alerting.go @@ -116,3 +116,49 @@ func (m *receiverCreateScopeMigration) Exec(sess *xorm.Session, mg *migrator.Mig func AddReceiverCreateScopeMigration(mg *migrator.Migrator) { mg.AddMigration("remove scope from alert.notifications.receivers:create", &receiverCreateScopeMigration{}) } + +type receiverProtectedFieldsEditor struct { + migrator.MigrationBase +} + +var _ migrator.CodeMigration = new(alertingMigrator) + +func (m *receiverProtectedFieldsEditor) SQL(migrator.Dialect) string { + return "code migration" +} + +func (m *receiverProtectedFieldsEditor) Exec(sess *xorm.Session, mg *migrator.Migrator) error { + sql := `SELECT * + FROM permission AS P + WHERE action = 'alert.notifications.receivers.secrets:read' + AND EXISTS(SELECT 1 FROM role AS R WHERE R.id = P.role_id AND R.name LIKE 'managed:%') + AND NOT EXISTS(SELECT 1 + FROM permission AS P2 + WHERE P2.role_id = P.role_id + AND P2.action = 'alert.notifications.receivers.protected:write' AND P2.scope = P.scope + )` + var results []accesscontrol.Permission + if err := sess.SQL(sql).Find(&results); err != nil { + return fmt.Errorf("failed to query permissions: %w", err) + } + + permissionsToCreate := make([]accesscontrol.Permission, 0, len(results)) + rolesAffected := make(map[int64][]string, 0) + for _, result := range results { + result.ID = 0 + result.Action = "alert.notifications.receivers.protected:write" + result.Created = time.Now() + result.Updated = time.Now() + permissionsToCreate = append(permissionsToCreate, result) + rolesAffected[result.RoleID] = append(rolesAffected[result.RoleID], result.Identifier) + } + _, err := sess.InsertMulti(&permissionsToCreate) + for id, ids := range rolesAffected { + mg.Logger.Debug("Added permission 'alert.notifications.receivers.protected:write' to managed role", "roleID", id, "identifiers", ids) + } + return err +} + +func AddReceiverProtectedFieldsEditor(mg *migrator.Migrator) { + mg.AddMigration("add 'alert.notifications.receivers.protected:write' to receiver admins", &receiverProtectedFieldsEditor{}) +} diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index 9f0191dc9ff..17dd7b9c068 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -168,4 +168,6 @@ func (oss *OSSMigrations) AddMigration(mg *Migrator) { ualert.CollateBinAlertRuleNamespace(mg) ualert.CollateBinAlertRuleGroup(mg) + + accesscontrol.AddReceiverProtectedFieldsEditor(mg) } diff --git a/pkg/tests/api/alerting/test-data/alert-notifiers-v1-snapshot.json b/pkg/tests/api/alerting/test-data/alert-notifiers-v1-snapshot.json index 7bf5ff10535..b3dabb7cde2 100644 --- a/pkg/tests/api/alerting/test-data/alert-notifiers-v1-snapshot.json +++ b/pkg/tests/api/alerting/test-data/alert-notifiers-v1-snapshot.json @@ -82,6 +82,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": true, "dependsOn": "", @@ -208,6 +209,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": true, "dependsOn": "", @@ -352,6 +354,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": true, "dependsOn": "", @@ -451,6 +454,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -748,6 +752,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -910,6 +915,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -1194,6 +1200,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -1392,6 +1399,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -1793,6 +1801,7 @@ "is": "" }, "required": false, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -1820,6 +1829,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -2318,6 +2328,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -2610,6 +2621,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": true, "dependsOn": "token", @@ -2628,6 +2640,7 @@ "is": "" }, "required": false, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -2953,6 +2966,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -3303,6 +3317,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": true, "dependsOn": "", @@ -3393,6 +3408,7 @@ "is": "" }, "required": false, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -3474,6 +3490,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -3916,6 +3933,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -4114,6 +4132,7 @@ "is": "" }, "required": false, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -4201,6 +4220,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": true, "dependsOn": "secret", diff --git a/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go b/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go index 0f7b476ecad..5497c8f1685 100644 --- a/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go +++ b/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go @@ -143,7 +143,7 @@ func TestIntegrationResourcePermissions(t *testing.T) { adminClient := test_common.NewReceiverClient(t, admin) writeACMetadata := []string{"canWrite", "canDelete"} - allACMetadata := []string{"canWrite", "canDelete", "canReadSecrets", "canAdmin"} + allACMetadata := []string{"canWrite", "canDelete", "canReadSecrets", "canAdmin", "canModifyProtected"} mustID := func(user apis.User) int64 { id, err := user.Identity.GetInternalID() @@ -404,13 +404,14 @@ func TestIntegrationAccessControl(t *testing.T) { org1 := helper.Org1 type testCase struct { - user apis.User - canRead bool - canUpdate bool - canCreate bool - canDelete bool - canReadSecrets bool - canAdmin bool + user apis.User + canRead bool + canUpdate bool + canUpdateProtected bool + canCreate bool + canDelete bool + canReadSecrets bool + canAdmin bool } // region users unauthorized := helper.CreateUser("unauthorized", "Org1", org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{}) @@ -473,20 +474,22 @@ func TestIntegrationAccessControl(t *testing.T) { testCases := []testCase{ { - user: unauthorized, - canRead: false, - canUpdate: false, - canCreate: false, - canDelete: false, + user: unauthorized, + canRead: false, + canUpdate: false, + canUpdateProtected: false, + canCreate: false, + canDelete: false, }, { - user: org1.Admin, - canRead: true, - canCreate: true, - canUpdate: true, - canDelete: true, - canAdmin: true, - canReadSecrets: true, + user: org1.Admin, + canRead: true, + canCreate: true, + canUpdate: true, + canUpdateProtected: true, + canDelete: true, + canAdmin: true, + canReadSecrets: true, }, { user: org1.Editor, @@ -535,22 +538,24 @@ func TestIntegrationAccessControl(t *testing.T) { canDelete: true, }, { - user: adminLikeUser, - canRead: true, - canCreate: true, - canUpdate: true, - canDelete: true, - canAdmin: true, - canReadSecrets: true, + user: adminLikeUser, + canRead: true, + canCreate: true, + canUpdate: true, + canUpdateProtected: true, + canDelete: true, + canAdmin: true, + canReadSecrets: true, }, { - user: adminLikeUserLongName, - canRead: true, - canCreate: true, - canUpdate: true, - canDelete: true, - canAdmin: true, - canReadSecrets: true, + user: adminLikeUserLongName, + canRead: true, + canCreate: true, + canUpdate: true, + canUpdateProtected: true, + canDelete: true, + canAdmin: true, + canReadSecrets: true, }, } @@ -609,6 +614,9 @@ func TestIntegrationAccessControl(t *testing.T) { if tc.canUpdate { expectedWithMetadata.SetAccessControl("canWrite") } + if tc.canUpdateProtected { + expectedWithMetadata.SetAccessControl("canModifyProtected") + } if tc.canDelete { expectedWithMetadata.SetAccessControl("canDelete") } @@ -672,6 +680,32 @@ func TestIntegrationAccessControl(t *testing.T) { require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err) }) }) + + updatedExpected = expected.Copy().(*v0alpha1.Receiver) + updatedExpected.Spec.Integrations = []v0alpha1.ReceiverIntegration{ + createIntegration(t, "webhook"), + } + + expected, err = adminClient.Update(ctx, updatedExpected, v1.UpdateOptions{}) + require.NoErrorf(t, err, "Payload %s", string(d)) + require.NotNil(t, expected) + + updatedProtected := expected.Copy().(*v0alpha1.Receiver) + updatedProtected.Spec.Integrations[0].Settings["url"] = "http://localhost:8080/webhook" + + if tc.canUpdateProtected { + t.Run("should be able to update protected fields of the receiver", func(t *testing.T) { + updated, err := client.Update(ctx, updatedProtected, v1.UpdateOptions{}) + require.NoErrorf(t, err, "Payload %s", string(d)) + require.NotNil(t, updated) + expected = updated + }) + } else { + t.Run("should be forbidden to edit protected fields of the receiver", func(t *testing.T) { + _, err := client.Update(ctx, updatedProtected, v1.UpdateOptions{}) + require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err) + }) + } } else { t.Run("should be forbidden to update receiver", func(t *testing.T) { _, err := client.Update(ctx, updatedExpected, v1.UpdateOptions{}) @@ -684,6 +718,7 @@ func TestIntegrationAccessControl(t *testing.T) { require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err) }) }) + require.Falsef(t, tc.canUpdateProtected, "Invalid combination of assertions. CanUpdateProtected should be false") } deleteOptions := v1.DeleteOptions{Preconditions: &v1.Preconditions{ResourceVersion: util.Pointer(expected.ResourceVersion)}} @@ -1291,6 +1326,7 @@ func TestIntegrationCRUD(t *testing.T) { receiver.SetAccessControl("canDelete") receiver.SetAccessControl("canReadSecrets") receiver.SetAccessControl("canAdmin") + receiver.SetAccessControl("canModifyProtected") receiver.SetInUse(0, nil) receiver.SetCanUse(true) diff --git a/pkg/tests/apis/alerting/notifications/receivers/test-data/imported-expected-snapshot.json b/pkg/tests/apis/alerting/notifications/receivers/test-data/imported-expected-snapshot.json index 621f9c3af16..f092d8980f7 100644 --- a/pkg/tests/apis/alerting/notifications/receivers/test-data/imported-expected-snapshot.json +++ b/pkg/tests/apis/alerting/notifications/receivers/test-data/imported-expected-snapshot.json @@ -8,6 +8,7 @@ "annotations": { "grafana.com/access/canAdmin": "true", "grafana.com/access/canDelete": "true", + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/access/canWrite": "true", "grafana.com/canUse": "true", @@ -40,6 +41,7 @@ "kind": "Receiver", "metadata": { "annotations": { + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/canUse": "false", "grafana.com/inUse/routes": "0", @@ -61,6 +63,7 @@ "kind": "Receiver", "metadata": { "annotations": { + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/canUse": "false", "grafana.com/inUse/routes": "0", @@ -105,6 +108,7 @@ "kind": "Receiver", "metadata": { "annotations": { + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/canUse": "false", "grafana.com/inUse/routes": "0", @@ -153,6 +157,7 @@ "kind": "Receiver", "metadata": { "annotations": { + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/canUse": "false", "grafana.com/inUse/routes": "0", @@ -211,6 +216,7 @@ "kind": "Receiver", "metadata": { "annotations": { + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/canUse": "false", "grafana.com/inUse/routes": "0", @@ -256,6 +262,7 @@ "kind": "Receiver", "metadata": { "annotations": { + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/canUse": "false", "grafana.com/inUse/routes": "0", @@ -317,6 +324,7 @@ "kind": "Receiver", "metadata": { "annotations": { + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/canUse": "false", "grafana.com/inUse/routes": "1", @@ -388,6 +396,7 @@ "kind": "Receiver", "metadata": { "annotations": { + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/canUse": "false", "grafana.com/inUse/routes": "0", @@ -441,6 +450,7 @@ "kind": "Receiver", "metadata": { "annotations": { + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/canUse": "false", "grafana.com/inUse/routes": "0", @@ -525,6 +535,7 @@ "kind": "Receiver", "metadata": { "annotations": { + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/canUse": "false", "grafana.com/inUse/routes": "0", @@ -579,6 +590,7 @@ "kind": "Receiver", "metadata": { "annotations": { + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/canUse": "false", "grafana.com/inUse/routes": "0", @@ -625,6 +637,7 @@ "kind": "Receiver", "metadata": { "annotations": { + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/canUse": "false", "grafana.com/inUse/routes": "0", @@ -674,6 +687,7 @@ "kind": "Receiver", "metadata": { "annotations": { + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/canUse": "false", "grafana.com/inUse/routes": "0", @@ -722,6 +736,7 @@ "kind": "Receiver", "metadata": { "annotations": { + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/canUse": "false", "grafana.com/inUse/routes": "1", @@ -767,6 +782,7 @@ "kind": "Receiver", "metadata": { "annotations": { + "grafana.com/access/canModifyProtected": "true", "grafana.com/access/canReadSecrets": "true", "grafana.com/canUse": "false", "grafana.com/inUse/routes": "0", diff --git a/public/app/features/alerting/unified/components/receivers/NewReceiverView.test.tsx b/public/app/features/alerting/unified/components/receivers/NewReceiverView.test.tsx index e1a1aa5aeba..ad6ee94d73e 100644 --- a/public/app/features/alerting/unified/components/receivers/NewReceiverView.test.tsx +++ b/public/app/features/alerting/unified/components/receivers/NewReceiverView.test.tsx @@ -1,6 +1,6 @@ import { Route, Routes } from 'react-router-dom-v5-compat'; import { render, screen } from 'test/test-utils'; -import { byLabelText, byPlaceholderText, byRole, byTestId } from 'testing-library-selector'; +import { byPlaceholderText, byRole, byTestId } from 'testing-library-selector'; import { captureRequests } from 'app/features/alerting/unified/mocks/server/events'; import { AccessControlAction } from 'app/types/accessControl'; @@ -122,7 +122,7 @@ const ui = { inputs: { name: byPlaceholderText('Name'), email: { - addresses: byLabelText(/Addresses/), + addresses: byRole('textbox', { name: /^Addresses/ }), }, }, }; diff --git a/public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx b/public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx index 16042c0e654..073b88598dc 100644 --- a/public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx @@ -30,8 +30,8 @@ export interface Props { * This is used to access the settings and secure fields for the integration in a type-safe way. */ integrationPrefix: `items.${number}`; + canEditProtectedFields: boolean; readOnly?: boolean; - customValidators?: Record['customValidator']>; } @@ -44,6 +44,7 @@ export function ChannelOptions({ integrationPrefix, readOnly = false, customValidators = {}, + canEditProtectedFields, }: Props): JSX.Element { const { watch } = useFormContext>(); @@ -54,7 +55,7 @@ export function ChannelOptions({ const getOptionMeta = (option: NotificationChannelOption): OptionMeta => ({ required: determineRequired(option, settings, secureFields), - readOnly: determineReadOnly(option, settings, secureFields), + readOnly: determineReadOnly(option, settings, secureFields, canEditProtectedFields), }); return ( @@ -78,6 +79,7 @@ export function ChannelOptions({ label={option.label} description={option.description} htmlFor={`${settingsPath}${option.propertyName}`} + noMargin > ({ ); } - const error: FieldError | DeepMap | undefined = ( - (option.secure ? errors?.secureFields : errors?.settings) as DeepMap | undefined - )?.[option.secureFieldKey ?? option.propertyName]; + const errorSource = option.secure ? errors?.secureFields : errors?.settings; + const propertyKey = option.secureFieldKey ?? option.propertyName; + const error = // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + (errorSource as Record, FieldError>> | undefined)?.[ + propertyKey + ]; const defaultValue = defaultValues?.settings?.[option.propertyName]; @@ -140,8 +145,14 @@ const determineRequired = ( const determineReadOnly = ( option: NotificationChannelOption, settings: Record, - secureFields: NotificationChannelSecureFields + secureFields: NotificationChannelSecureFields, + canEditProtectedFields: boolean ) => { + if (option.protected && !canEditProtectedFields) { + return true; + } + + // Handle fields with dependencies (e.g., field B depends on field A being set) if (!option.dependsOn) { return false; } diff --git a/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.test.tsx b/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.test.tsx index 4fda03e9d53..00a933acec3 100644 --- a/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.test.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.test.tsx @@ -89,6 +89,7 @@ describe('ChannelSubForm', () => { commonSettingsComponent={GrafanaCommonChannelSettings} isEditable={true} isTestable={false} + canEditProtectedFields={true} /> diff --git a/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx b/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx index 645c6565a01..c49b5184623 100644 --- a/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx @@ -35,6 +35,7 @@ interface Props { onDelete?: () => void; isEditable?: boolean; isTestable?: boolean; + canEditProtectedFields: boolean; customValidators?: React.ComponentProps['customValidators']; } @@ -52,6 +53,7 @@ export function ChannelSubForm({ commonSettingsComponent: CommonSettingsComponent, isEditable = true, isTestable, + canEditProtectedFields, customValidators = {}, }: Props): JSX.Element { const styles = useStyles2(getStyles); @@ -210,6 +212,7 @@ export function ChannelSubForm({ label={t('alerting.channel-sub-form.label-integration', 'Integration')} htmlFor={contactPointTypeInputId} data-testid={`${pathPrefix}type`} + noMargin > ({ onDeleteSubform={onDeleteSubform} integrationPrefix={channelFieldPath} readOnly={!isEditable} + canEditProtectedFields={canEditProtectedFields} customValidators={customValidators} /> {!!(mandatoryOptions.length && optionalOptions.length) && ( @@ -301,6 +305,7 @@ export function ChannelSubForm({ errors={errors} integrationPrefix={channelFieldPath} readOnly={!isEditable} + canEditProtectedFields={canEditProtectedFields} customValidators={customValidators} /> diff --git a/public/app/features/alerting/unified/components/receivers/form/CloudReceiverForm.tsx b/public/app/features/alerting/unified/components/receivers/form/CloudReceiverForm.tsx index 6512dcf86b3..ea490821452 100644 --- a/public/app/features/alerting/unified/components/receivers/form/CloudReceiverForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/CloudReceiverForm.tsx @@ -91,6 +91,7 @@ export const CloudReceiverForm = ({ contactPoint, alertManagerSourceName, readOn alertManagerSourceName={alertManagerSourceName} defaultItem={defaultChannelValues} commonSettingsComponent={CloudCommonChannelSettings} + canEditProtectedFields={true} /> ); diff --git a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx index de17ab6c7c0..df68ab185dc 100644 --- a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx @@ -9,7 +9,7 @@ import { } from 'app/features/alerting/unified/components/contact-points/useContactPoints'; import { showManageContactPointPermissions } from 'app/features/alerting/unified/components/contact-points/utils'; import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource'; -import { canEditEntity } from 'app/features/alerting/unified/utils/k8s/utils'; +import { canEditEntity, canModifyProtectedEntity } from 'app/features/alerting/unified/utils/k8s/utils'; import { GrafanaManagedContactPoint, GrafanaManagedReceiverConfig, @@ -124,8 +124,10 @@ export const GrafanaReceiverForm = ({ contactPoint, readOnly = false, editMode } // If there is no contact point it means we're creating a new one, so scoped permissions doesn't exist yet const hasScopedEditPermissions = contactPoint ? canEditEntity(contactPoint) : true; + const hasScopedEditProtectedPermissions = contactPoint ? canModifyProtectedEntity(contactPoint) : true; const isEditable = !readOnly && hasScopedEditPermissions && !contactPoint?.provisioned; const isTestable = !readOnly; + const canEditProtectedFields = editMode ? hasScopedEditProtectedPermissions : true; if (isLoadingNotifiers || isLoadingOnCallIntegration) { return ( @@ -178,6 +180,7 @@ export const GrafanaReceiverForm = ({ contactPoint, readOnly = false, editMode } canManagePermissions={ editMode && contactPoint && showManageContactPointPermissions(GRAFANA_RULES_SOURCE_NAME, contactPoint) } + canEditProtectedFields={canEditProtectedFields} /> {testReceivers && ( { showDefaultRouteWarning?: boolean; contactPointId?: string; canManagePermissions?: boolean; + canEditProtectedFields: boolean; } export function ReceiverForm({ @@ -58,6 +59,7 @@ export function ReceiverForm({ showDefaultRouteWarning, contactPointId, canManagePermissions, + canEditProtectedFields, }: Props) { const notifyApp = useAppNotification(); const styles = useStyles2(getStyles); @@ -66,15 +68,16 @@ export function ReceiverForm({ // normalize deprecated and new config values const normalizedConfig = normalizeFormValues(initialValues); - const defaultValues = normalizedConfig ?? { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const defaultValues = (normalizedConfig ?? { name: '', items: [ { ...defaultItem, __id: String(Math.random()), - } as any, + }, ], - }; + }) as ReceiverFormValues; const formAPI = useForm>({ // making a copy here beacuse react-hook-form will mutate these, and break if the object is frozen. for real. @@ -148,6 +151,7 @@ export function ReceiverForm({ invalid={!!errors.name} error={errors.name && errors.name.message} required + noMargin > ({ onDelete={() => remove(index)} pathPrefix={pathPrefix} notifiers={notifiers} - errors={errors?.items?.[index] as FieldErrors} + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + errors={errors?.items?.[index] as FieldErrors | undefined} commonSettingsComponent={commonSettingsComponent} isEditable={isEditable} isTestable={isTestable} + canEditProtectedFields={canEditProtectedFields} customValidators={customValidators ? customValidators[field.type] : undefined} /> ); diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.test.tsx b/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.test.tsx new file mode 100644 index 00000000000..c52b4bfeca5 --- /dev/null +++ b/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.test.tsx @@ -0,0 +1,448 @@ +import userEvent from '@testing-library/user-event'; +import { FormProvider, useForm } from 'react-hook-form'; +import { render, screen, waitFor } from 'test/test-utils'; + +import { + NotificationChannelOption, + NotificationChannelSecureFields, + OptionMeta, +} from 'app/features/alerting/unified/types/alerting'; + +import { OptionField } from './OptionField'; + +const TestWrapper = ({ children }: { children: React.ReactNode }) => { + const methods = useForm(); + return {children}; +}; + +const renderOptionField = ( + option: NotificationChannelOption, + props: { + getOptionMeta?: (option: NotificationChannelOption) => OptionMeta; + readOnly?: boolean; + secureFields?: NotificationChannelSecureFields; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + defaultValue?: any; + } = {} +) => { + const defaultProps = { + option, + defaultValue: '', + pathPrefix: 'test.', + secureFields: {}, + ...props, + }; + + return render( + + + + ); +}; + +describe('OptionField', () => { + describe('Protected field indicator', () => { + it('should display lock icon with tooltip when field is protected and readOnly', async () => { + const option: NotificationChannelOption = { + propertyName: 'testField', + label: 'Test Field', + description: 'A test field', + element: 'input', + inputType: 'text', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: true, + dependsOn: '', + }; + + const getOptionMeta = jest.fn().mockReturnValue({ readOnly: true, required: false }); + + renderOptionField(option, { getOptionMeta }); + + // Check that lock icon is displayed + const lockIcon = screen.getByTestId('lock-icon'); + expect(lockIcon).toBeInTheDocument(); + + // Hover over the icon to show tooltip + await userEvent.hover(lockIcon); + + // Check that tooltip appears with correct text + await waitFor(() => { + expect( + screen.getByText('This field is protected and can only be edited by users with elevated permissions') + ).toBeInTheDocument(); + }); + }); + + it('should NOT display lock icon when field is protected but NOT readOnly', () => { + const option: NotificationChannelOption = { + propertyName: 'testField', + label: 'Test Field', + description: 'A test field', + element: 'input', + inputType: 'text', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: true, + dependsOn: '', + }; + + const getOptionMeta = jest.fn().mockReturnValue({ readOnly: false, required: false }); + + renderOptionField(option, { getOptionMeta }); + + // Lock icon should not be displayed + expect(screen.queryByTestId('lock-icon')).not.toBeInTheDocument(); + }); + + it('should NOT display lock icon when field is NOT protected', () => { + const option: NotificationChannelOption = { + propertyName: 'testField', + label: 'Test Field', + description: 'A test field', + element: 'input', + inputType: 'text', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: false, + dependsOn: '', + }; + + const getOptionMeta = jest.fn().mockReturnValue({ readOnly: true, required: false }); + + renderOptionField(option, { getOptionMeta }); + + // Lock icon should not be displayed + expect(screen.queryByTestId('lock-icon')).not.toBeInTheDocument(); + }); + + it('should NOT display lock icon when getOptionMeta is not provided', () => { + const option: NotificationChannelOption = { + propertyName: 'testField', + label: 'Test Field', + description: 'A test field', + element: 'input', + inputType: 'text', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: true, + dependsOn: '', + }; + + renderOptionField(option); + + // Lock icon should not be displayed + expect(screen.queryByTestId('lock-icon')).not.toBeInTheDocument(); + }); + + it('should display lock icon for checkbox fields when protected and readOnly', () => { + const option: NotificationChannelOption = { + propertyName: 'testCheckbox', + label: 'Test Checkbox', + description: 'A test checkbox', + element: 'checkbox', + inputType: '', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: true, + dependsOn: '', + }; + + const getOptionMeta = jest.fn().mockReturnValue({ readOnly: true, required: false }); + + renderOptionField(option, { getOptionMeta }); + + // Lock icon should be displayed even for checkbox + const lockIcon = screen.getByTestId('lock-icon'); + expect(lockIcon).toBeInTheDocument(); + }); + + it('should display lock icon for select fields when protected and readOnly', () => { + const option: NotificationChannelOption = { + propertyName: 'testSelect', + label: 'Test Select', + description: 'A test select', + element: 'select', + inputType: '', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: true, + dependsOn: '', + selectOptions: [ + { label: 'Option 1', value: 'opt1' }, + { label: 'Option 2', value: 'opt2' }, + ], + }; + + const getOptionMeta = jest.fn().mockReturnValue({ readOnly: true, required: false }); + + renderOptionField(option, { getOptionMeta }); + + // Lock icon should be displayed + const lockIcon = screen.getByTestId('lock-icon'); + expect(lockIcon).toBeInTheDocument(); + }); + }); + + describe('Subform fields', () => { + it('should pass getOptionMeta to SubformField component', () => { + const getOptionMeta = jest.fn().mockReturnValue({ readOnly: true, required: false }); + + const option: NotificationChannelOption = { + propertyName: 'testSubform', + label: 'Test Subform', + description: 'A test subform', + element: 'subform', + inputType: '', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: false, + dependsOn: '', + subformOptions: [ + { + propertyName: 'nestedField', + label: 'Nested Field', + description: 'A nested field', + element: 'input', + inputType: 'text', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: true, + dependsOn: '', + }, + ], + }; + + renderOptionField(option, { getOptionMeta, defaultValue: { nestedField: 'test' } }); + + // The subform should be rendered with the nested field + expect(screen.getByText('Test Subform')).toBeInTheDocument(); + + // Verify that getOptionMeta was called for the nested field + // This ensures it was passed through to the SubformField component + expect(getOptionMeta).toHaveBeenCalled(); + }); + + it('should display lock icon for protected fields inside subform when readOnly', async () => { + const getOptionMeta = jest.fn((opt) => { + // Make the nested protected field readOnly + if (opt.protected) { + return { readOnly: true, required: false }; + } + return { readOnly: false, required: false }; + }); + + const option: NotificationChannelOption = { + propertyName: 'oauth2', + label: 'OAuth2 Configuration', + description: 'OAuth2 settings', + element: 'subform', + inputType: '', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: false, + dependsOn: '', + subformOptions: [ + { + propertyName: 'token_url', + label: 'Token URL', + description: 'OAuth2 token URL', + element: 'input', + inputType: 'text', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: true, + dependsOn: '', + }, + ], + }; + + renderOptionField(option, { getOptionMeta, defaultValue: { token_url: 'https://example.com/token' } }); + + // Check that lock icon is displayed for the nested protected field + const lockIcon = screen.getByTestId('lock-icon'); + expect(lockIcon).toBeInTheDocument(); + + // Hover over the icon to show tooltip + await userEvent.hover(lockIcon); + + // Check that tooltip appears + await waitFor(() => { + expect( + screen.getByText('This field is protected and can only be edited by users with elevated permissions') + ).toBeInTheDocument(); + }); + }); + + it('should NOT display lock icon for protected fields inside subform when user can edit', () => { + const getOptionMeta = jest.fn().mockReturnValue({ readOnly: false, required: false }); + + const option: NotificationChannelOption = { + propertyName: 'oauth2', + label: 'OAuth2 Configuration', + description: 'OAuth2 settings', + element: 'subform', + inputType: '', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: false, + dependsOn: '', + subformOptions: [ + { + propertyName: 'token_url', + label: 'Token URL', + description: 'OAuth2 token URL', + element: 'input', + inputType: 'text', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: true, + dependsOn: '', + }, + ], + }; + + renderOptionField(option, { getOptionMeta, defaultValue: { token_url: 'https://example.com/token' } }); + + // Lock icon should not be displayed when user has permission + expect(screen.queryByTestId('lock-icon')).not.toBeInTheDocument(); + }); + }); + + describe('Subform array fields', () => { + it('should pass getOptionMeta to SubformArrayField component', () => { + const getOptionMeta = jest.fn().mockReturnValue({ readOnly: true, required: false }); + + const option: NotificationChannelOption = { + propertyName: 'testSubformArray', + label: 'Test Subform Array', + description: 'A test subform array', + element: 'subform_array', + inputType: '', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: false, + dependsOn: '', + subformOptions: [ + { + propertyName: 'nestedField', + label: 'Nested Field', + description: 'A nested field', + element: 'input', + inputType: 'text', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: true, + dependsOn: '', + }, + ], + }; + + renderOptionField(option, { getOptionMeta, defaultValue: [{ nestedField: 'test' }] }); + + // The subform array should be rendered + expect(screen.getByText('Test Subform Array (1)')).toBeInTheDocument(); + + // Verify that getOptionMeta was called + expect(getOptionMeta).toHaveBeenCalled(); + }); + + it('should display lock icon for protected fields inside subform array when readOnly', async () => { + const getOptionMeta = jest.fn((opt) => { + if (opt.protected) { + return { readOnly: true, required: false }; + } + return { readOnly: false, required: false }; + }); + + const option: NotificationChannelOption = { + propertyName: 'headers', + label: 'HTTP Headers', + description: 'Custom headers', + element: 'subform_array', + inputType: '', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: false, + dependsOn: '', + subformOptions: [ + { + propertyName: 'authorization', + label: 'Authorization Header', + description: 'Auth header value', + element: 'input', + inputType: 'text', + placeholder: '', + required: false, + secure: false, + showWhen: { field: '', is: '' }, + validationRule: '', + protected: true, + dependsOn: '', + }, + ], + }; + + renderOptionField(option, { getOptionMeta, defaultValue: [{ authorization: 'Bearer token' }] }); + + // Check that lock icon is displayed for the nested protected field + const lockIcon = screen.getByTestId('lock-icon'); + expect(lockIcon).toBeInTheDocument(); + + // Hover over the icon to show tooltip + await userEvent.hover(lockIcon); + + // Check that tooltip appears + await waitFor(() => { + expect( + screen.getByText('This field is protected and can only be edited by users with elevated permissions') + ).toBeInTheDocument(); + }); + }); + }); +}); diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx b/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx index d965fafb40d..f037a127b64 100644 --- a/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx @@ -3,15 +3,19 @@ import { FC } from 'react'; import { Controller, DeepMap, FieldError, useFormContext } from 'react-hook-form'; import { GrafanaTheme2 } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { Checkbox, Field, + Icon, Input, RadioButtonList, SecretInput, SecretTextArea, Select, + Stack, TextArea, + Tooltip, useStyles2, } from '@grafana/ui'; import { @@ -64,6 +68,7 @@ export const OptionField: FC = ({ errors={error} pathPrefix={pathPrefix} onDelete={onDeleteSubform} + getOptionMeta={getOptionMeta} /> ); } @@ -76,13 +81,34 @@ export const OptionField: FC = ({ option={option} pathPrefix={pathPrefix} errors={error as Array> | undefined} + getOptionMeta={getOptionMeta} /> ); } + const shouldShowProtectedIndicator = option.protected && getOptionMeta?.(option).readOnly; + + const labelText = option.element !== 'checkbox' && option.element !== 'radio' ? option.label : undefined; + + const label = shouldShowProtectedIndicator ? ( + + + + + {labelText} + + ) : ( + labelText + ); + return ( > | null; defaultValue?: SelectableValue; showWhen: { field: string; is: string | boolean }; diff --git a/public/app/features/alerting/unified/utils/k8s/constants.ts b/public/app/features/alerting/unified/utils/k8s/constants.ts index 1eb3c371d59..cf297261733 100644 --- a/public/app/features/alerting/unified/utils/k8s/constants.ts +++ b/public/app/features/alerting/unified/utils/k8s/constants.ts @@ -21,6 +21,8 @@ export enum K8sAnnotations { AccessAdmin = 'grafana.com/access/canAdmin', /** Annotation key that indicates that the calling user is able to delete this entity */ AccessDelete = 'grafana.com/access/canDelete', + /** Annotation key that indicates that the calling user is able to modify protected fields of this entity */ + AccessModifyProtected = 'grafana.com/access/canModifyProtected', } /** diff --git a/public/app/features/alerting/unified/utils/k8s/utils.ts b/public/app/features/alerting/unified/utils/k8s/utils.ts index 8aecb9dd46b..48ec5685a69 100644 --- a/public/app/features/alerting/unified/utils/k8s/utils.ts +++ b/public/app/features/alerting/unified/utils/k8s/utils.ts @@ -42,6 +42,9 @@ export const canAdminEntity = (k8sEntity: EntityToCheck) => export const canDeleteEntity = (k8sEntity: EntityToCheck) => getAnnotation(k8sEntity, K8sAnnotations.AccessDelete) === 'true'; +export const canModifyProtectedEntity = (k8sEntity: EntityToCheck) => + getAnnotation(k8sEntity, K8sAnnotations.AccessModifyProtected) === 'true'; + /** * Escape \ and = characters for field selectors. * The Kubernetes API Machinery will decode those automatically. diff --git a/public/app/types/accessControl.ts b/public/app/types/accessControl.ts index b759bc32d6f..c53bdb9389e 100644 --- a/public/app/types/accessControl.ts +++ b/public/app/types/accessControl.ts @@ -143,6 +143,7 @@ export enum AccessControlAction { AlertingReceiversCreate = 'alert.notifications.receivers:create', AlertingReceiversWrite = 'alert.notifications.receivers:write', AlertingReceiversRead = 'alert.notifications.receivers:read', + AlertingReceiversUpdateProtected = 'alert.notifications.receivers.protected:write', // Alerting routes actions AlertingRoutesRead = 'alert.notifications.routes:read', diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index eef8a1bdd7d..f62b26211f6 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -2232,6 +2232,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "Open external link" }, + "receivers": { + "protected": { + "field": { + "description": "This field is protected and can only be edited by users with elevated permissions" + } + } + }, "receivers-section": { "button-more": "More", "new-menu": { From c647140475a414fc181c4ad78a2c20ba785733ba Mon Sep 17 00:00:00 2001 From: Renato Costa <103441181+renatolabs@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:19:45 -0500 Subject: [PATCH 168/297] unified-storage: add `Delete` support to the sqlkv implementation (#115443) --- .../unified/resource/data/sqlkv_delete.sql | 3 + pkg/storage/unified/resource/kv.go | 3 + pkg/storage/unified/resource/sqlkv.go | 68 +++++++++++++++---- pkg/storage/unified/testing/kv.go | 45 +++++++----- pkg/storage/unified/testing/kv_test.go | 1 - 5 files changed, 87 insertions(+), 33 deletions(-) create mode 100644 pkg/storage/unified/resource/data/sqlkv_delete.sql diff --git a/pkg/storage/unified/resource/data/sqlkv_delete.sql b/pkg/storage/unified/resource/data/sqlkv_delete.sql new file mode 100644 index 00000000000..60a60746993 --- /dev/null +++ b/pkg/storage/unified/resource/data/sqlkv_delete.sql @@ -0,0 +1,3 @@ +DELETE +FROM {{ .Ident .TableName }} +WHERE {{ .Ident "key_path" }} = {{ .Arg .KeyPath }}; diff --git a/pkg/storage/unified/resource/kv.go b/pkg/storage/unified/resource/kv.go index 0af2cc3c4a1..0bd413254a4 100644 --- a/pkg/storage/unified/resource/kv.go +++ b/pkg/storage/unified/resource/kv.go @@ -228,6 +228,9 @@ func (k *badgerKV) Delete(ctx context.Context, section string, key string) error if section == "" { return fmt.Errorf("section is required") } + if key == "" { + return fmt.Errorf("key is required") + } txn := k.db.NewTransaction(true) defer txn.Discard() diff --git a/pkg/storage/unified/resource/sqlkv.go b/pkg/storage/unified/resource/sqlkv.go index c943b5292f6..90171a77a5f 100644 --- a/pkg/storage/unified/resource/sqlkv.go +++ b/pkg/storage/unified/resource/sqlkv.go @@ -33,9 +33,13 @@ func mustTemplate(filename string) *template.Template { // Templates. var ( - sqlKVGet = mustTemplate("sqlkv_get.sql") + sqlKVGet = mustTemplate("sqlkv_get.sql") + sqlKVDelete = mustTemplate("sqlkv_delete.sql") ) +// sqlKVSection can be embedded in structs used when rendering query templates +// for queries that reference a particular section. The section will be validated, +// and the template can directly reference the `TableName`. type sqlKVSection struct { Section string } @@ -60,18 +64,15 @@ func (req sqlKVSection) TableName() string { return "resource_events" } -type sqlKVGetRequest struct { - sqltemplate.SQLTemplate +// sqlKVSectionKey can be embedded in structs used when rendering query templates +// for queries that reference both a section and a particular key. The `key` is +// validated, and the template can reference the corresponding `KeyPath`. +type sqlKVSectionKey struct { sqlKVSection Key string - *sqlKVGetResponse } -type sqlKVGetResponse struct { - Value []byte -} - -func (req sqlKVGetRequest) Validate() error { +func (req sqlKVSectionKey) Validate() error { if err := req.sqlKVSection.Validate(); err != nil { return err } @@ -82,12 +83,35 @@ func (req sqlKVGetRequest) Validate() error { return nil } +func (req sqlKVSectionKey) KeyPath() string { + return req.Section + "/" + req.Key +} + +type sqlKVGetRequest struct { + sqltemplate.SQLTemplate + sqlKVSectionKey + *sqlKVGetResponse +} + +type sqlKVGetResponse struct { + Value []byte +} + +func (req sqlKVGetRequest) Validate() error { + return req.sqlKVSectionKey.Validate() +} + func (req sqlKVGetRequest) Results() ([]byte, error) { return req.Value, nil } -func (req sqlKVGetRequest) KeyPath() string { - return req.Section + "/" + req.Key +type sqlKVDeleteRequest struct { + sqltemplate.SQLTemplate + sqlKVSectionKey +} + +func (req sqlKVDeleteRequest) Validate() error { + return req.sqlKVSectionKey.Validate() } var _ KV = &sqlKV{} @@ -134,8 +158,7 @@ func (k *sqlKV) Keys(ctx context.Context, section string, opt ListOptions) iter. func (k *sqlKV) Get(ctx context.Context, section string, key string) (io.ReadCloser, error) { value, err := dbutil.QueryRow(ctx, k.db, sqlKVGet, sqlKVGetRequest{ SQLTemplate: sqltemplate.New(k.dialect), - sqlKVSection: sqlKVSection{section}, - Key: key, + sqlKVSectionKey: sqlKVSectionKey{sqlKVSection{section}, key}, sqlKVGetResponse: new(sqlKVGetResponse), }) if err != nil { @@ -171,7 +194,24 @@ func (k *sqlKV) Save(ctx context.Context, section string, key string) (io.WriteC } func (k *sqlKV) Delete(ctx context.Context, section string, key string) error { - panic("not implemented!") + res, err := dbutil.Exec(ctx, k.db, sqlKVDelete, sqlKVDeleteRequest{ + SQLTemplate: sqltemplate.New(k.dialect), + sqlKVSectionKey: sqlKVSectionKey{sqlKVSection{section}, key}, + }) + if err != nil { + return fmt.Errorf("failed to delete key: %w", err) + } + + rows, err := res.RowsAffected() + if err != nil { + return fmt.Errorf("failed to validate delete: %w", err) + } + + if rows == 0 { + return ErrNotFound + } + + return nil } func (k *sqlKV) BatchDelete(ctx context.Context, section string, keys []string) error { diff --git a/pkg/storage/unified/testing/kv.go b/pkg/storage/unified/testing/kv.go index 9688c5c1029..fbdb8c70d20 100644 --- a/pkg/storage/unified/testing/kv.go +++ b/pkg/storage/unified/testing/kv.go @@ -28,6 +28,10 @@ const ( TestKVUnixTimestamp = "unix timestamp" TestKVBatchGet = "batch get operations" TestKVBatchDelete = "batch delete operations" + + // Use `eventsSection` as the section for the tests, as the sqlkv implementation + // needs a real section to determine which table to use. + testSection = "unified/events" ) // NewKVFunc is a function that creates a new KV instance for testing @@ -84,22 +88,21 @@ func RunKVTest(t *testing.T, newKV NewKVFunc, opts *KVTestOptions) { } } +func prefixKey(nsPrefix, key string) string { + return nsPrefix + "/" + key +} + func runTestKVGet(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) - // Use `eventsSection` as the section for these tests, as the sqlkv implementation - // needs a real section to determine which table to use, and apply `nsPrefix` on - // the key itself. - section := "unified/events" - keyPrefix := nsPrefix + "-get" - prefixed := func(name string) string { return keyPrefix + "/" + name } t.Run("get existing key", func(t *testing.T) { // First save a key + existingKey := prefixKey(nsPrefix, "existing-key") testValue := "test value for get" - saveKVHelper(t, kv, ctx, section, prefixed("existing-key"), strings.NewReader(testValue)) + saveKVHelper(t, kv, ctx, testSection, existingKey, strings.NewReader(testValue)) // Now get it - reader, err := kv.Get(ctx, section, prefixed("existing-key")) + reader, err := kv.Get(ctx, testSection, existingKey) require.NoError(t, err) // Read the value @@ -113,19 +116,19 @@ func runTestKVGet(t *testing.T, kv resource.KV, nsPrefix string) { }) t.Run("get non-existent key", func(t *testing.T) { - _, err := kv.Get(ctx, section, prefixed("non-existent-key")) + _, err := kv.Get(ctx, testSection, prefixKey(nsPrefix, "non-existent-key")) assert.Error(t, err) assert.Equal(t, resource.ErrNotFound, err) }) t.Run("get with empty section", func(t *testing.T) { - _, err := kv.Get(ctx, "", prefixed("some-key")) + _, err := kv.Get(ctx, "", prefixKey(nsPrefix, "some-key")) assert.Error(t, err) assert.Contains(t, err.Error(), "section is required") }) t.Run("get with empty key", func(t *testing.T) { - _, err := kv.Get(ctx, section, "") + _, err := kv.Get(ctx, testSection, "") assert.Error(t, err) assert.Contains(t, err.Error(), "key is required") }) @@ -209,37 +212,43 @@ func runTestKVSave(t *testing.T, kv resource.KV, nsPrefix string) { func runTestKVDelete(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) - section := nsPrefix + "-delete" t.Run("delete existing key", func(t *testing.T) { // First create a key - saveKVHelper(t, kv, ctx, section, "delete-key", strings.NewReader("delete me")) + deleteKey := prefixKey(nsPrefix, "delete-key") + saveKVHelper(t, kv, ctx, testSection, deleteKey, strings.NewReader("delete me")) // Verify it exists - _, err := kv.Get(ctx, section, "delete-key") + _, err := kv.Get(ctx, testSection, deleteKey) require.NoError(t, err) // Delete it - err = kv.Delete(ctx, section, "delete-key") + err = kv.Delete(ctx, testSection, deleteKey) require.NoError(t, err) // Verify it's gone - _, err = kv.Get(ctx, section, "delete-key") + _, err = kv.Get(ctx, testSection, deleteKey) assert.Error(t, err) assert.Equal(t, resource.ErrNotFound, err) }) t.Run("delete non-existent key", func(t *testing.T) { - err := kv.Delete(ctx, section, "non-existent-delete-key") + err := kv.Delete(ctx, testSection, prefixKey(nsPrefix, "non-existent-delete-key")) assert.Error(t, err) assert.Equal(t, resource.ErrNotFound, err) }) t.Run("delete with empty section", func(t *testing.T) { - err := kv.Delete(ctx, "", "some-key") + err := kv.Delete(ctx, "", prefixKey(nsPrefix, "some-key")) assert.Error(t, err) assert.Contains(t, err.Error(), "section is required") }) + + t.Run("delete with empty key", func(t *testing.T) { + err := kv.Delete(ctx, testSection, "") + assert.Error(t, err) + assert.Contains(t, err.Error(), "key is required") + }) } func runTestKVKeys(t *testing.T, kv resource.KV, nsPrefix string) { diff --git a/pkg/storage/unified/testing/kv_test.go b/pkg/storage/unified/testing/kv_test.go index 4fc343d450e..f6db1d2f1d8 100644 --- a/pkg/storage/unified/testing/kv_test.go +++ b/pkg/storage/unified/testing/kv_test.go @@ -48,7 +48,6 @@ func TestSQLKV(t *testing.T) { NSPrefix: "sql-kv-test", SkipTests: map[string]bool{ TestKVSave: true, - TestKVDelete: true, TestKVKeys: true, TestKVKeysWithLimits: true, TestKVKeysWithSort: true, From dfd396b06ce427e385b6d3ae687c00a467d13bb6 Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Tue, 16 Dec 2025 22:39:21 +0100 Subject: [PATCH 169/297] Dashboard: Support Angular panel migrations in v2 schema (#115368) * V2: Support angular panel migrations * Add autoMigrateFrom and update ensureV2 * Remove context files * Simplify angular migration handler * linter * Only include angular options in angular panel data --- .../dashboard/v0alpha1/dashboard_kind.cue | 4 + .../apis/dashboard/v1beta1/dashboard_kind.cue | 4 + apps/dashboard/pkg/migration/README.md | 71 + .../input/v1beta1.angular-migrations.json | 1310 +++++++++++ ...mig-v10.table_thresholds.v42.v2alpha1.json | 58 +- ...-mig-v10.table_thresholds.v42.v2beta1.json | 58 +- ...-mig-v11.no-op-migration.v42.v2alpha1.json | 28 +- ...1-mig-v11.no-op-migration.v42.v2beta1.json | 28 +- ...mig-v13.graph_thresholds.v42.v2alpha1.json | 70 +- ...-mig-v13.graph_thresholds.v42.v2beta1.json | 70 +- ...v13.minimal_graph_config.v42.v2alpha1.json | 7 +- ...-v13.minimal_graph_config.v42.v2beta1.json | 7 +- ...osshair_to_graph_tooltip.v42.v2alpha1.json | 28 +- ...rosshair_to_graph_tooltip.v42.v2beta1.json | 28 +- ...-mig-v15.no-op-migration.v42.v2alpha1.json | 28 +- ...1-mig-v15.no-op-migration.v42.v2beta1.json | 28 +- ...-v16.grid_layout_upgrade.v42.v2alpha1.json | 39 +- ...g-v16.grid_layout_upgrade.v42.v2beta1.json | 39 +- ...v17.minspan_to_maxperrow.v42.v2alpha1.json | 42 +- ...-v17.minspan_to_maxperrow.v42.v2beta1.json | 42 +- ...a1-mig-v18.gauge_options.v42.v2alpha1.json | 4 + ...ta1-mig-v18.gauge_options.v42.v2beta1.json | 4 + ...eta1-mig-v19.panel_links.v42.v2alpha1.json | 14 +- ...beta1-mig-v19.panel_links.v42.v2beta1.json | 14 +- ...g-v2.panels-and-services.v42.v2alpha1.json | 38 +- ...ig-v2.panels-and-services.v42.v2beta1.json | 38 +- ...ta_links_series_to_field.v42.v2alpha1.json | 4 + ...ata_links_series_to_field.v42.v2beta1.json | 4 + ...ig-v22.table_panel_align.v42.v2alpha1.json | 20 +- ...mig-v22.table_panel_align.v42.v2beta1.json | 20 +- ...a1-mig-v24.table-angular.v42.v2alpha1.json | 362 ++- ...ta1-mig-v24.table-angular.v42.v2beta1.json | 362 ++- ...-mig-v25.no-op-migration.v42.v2alpha1.json | 13 +- ...1-mig-v25.no-op-migration.v42.v2beta1.json | 13 +- ...ls_and_constant_variable.v42.v2alpha1.json | 14 +- ...els_and_constant_variable.v42.v2beta1.json | 14 +- ..._and_variable_properties.v42.v2alpha1.json | 84 +- ...t_and_variable_properties.v42.v2beta1.json | 84 +- ...v28.singlestat_migration.v42.v2alpha1.json | 166 +- ...-v28.singlestat_migration.v42.v2beta1.json | 166 +- ...ings_and_tooltip_options.v42.v2alpha1.json | 4 + ...pings_and_tooltip_options.v42.v2beta1.json | 4 + ...-mig-v32.no_op_migration.v42.v2alpha1.json | 13 +- ...1-mig-v32.no_op_migration.v42.v2beta1.json | 13 +- ...v33.panel_ds_name_to_ref.v42.v2alpha1.json | 7 +- ...-v33.panel_ds_name_to_ref.v42.v2beta1.json | 7 +- ...v37.legend_normalization.v42.v2alpha1.json | 8 + ...-v37.legend_normalization.v42.v2beta1.json | 8 + ...isplaymode_comprehensive.v42.v2alpha1.json | 7 +- ...displaymode_comprehensive.v42.v2beta1.json | 7 +- ...eries_table_display_mode.v42.v2alpha1.json | 7 +- ...series_table_display_mode.v42.v2beta1.json | 7 +- ...ansform_timeseries_table.v42.v2alpha1.json | 14 +- ...ransform_timeseries_table.v42.v2beta1.json | 14 +- .../v1beta1-mig-v4.no-op.v42.v2alpha1.json | 21 +- .../v1beta1-mig-v4.no-op.v42.v2beta1.json | 21 +- ...mig-v42.hidefrom_tooltip.v42.v2alpha1.json | 7 +- ...-mig-v42.hidefrom_tooltip.v42.v2beta1.json | 7 +- .../v1beta1-mig-v5.no-op.v42.v2alpha1.json | 21 +- .../v1beta1-mig-v5.no-op.v42.v2beta1.json | 21 +- ...pulldowns_and_templating.v42.v2alpha1.json | 14 +- ....pulldowns_and_templating.v42.v2beta1.json | 14 +- ...a1-mig-v8.influxdb_query.v42.v2alpha1.json | 14 +- ...ta1-mig-v8.influxdb_query.v42.v2beta1.json | 14 +- .../v1beta1-mig-v9.no-op.v42.v2alpha1.json | 21 +- .../v1beta1-mig-v9.no-op.v42.v2beta1.json | 21 +- .../v0alpha1.influxdb-logs.v42.v2alpha1.json | 59 + .../v0alpha1.influxdb-logs.v42.v2beta1.json | 59 + ...lpha1.influxdb-templated.v42.v2alpha1.json | 73 +- ...alpha1.influxdb-templated.v42.v2beta1.json | 73 +- .../v0alpha1.mssql_fakedata.v42.v2alpha1.json | 226 +- .../v0alpha1.mssql_fakedata.v42.v2beta1.json | 226 +- .../v0alpha1.mssql_unittest.v42.v2alpha1.json | 1969 ++++++++++++++++- .../v0alpha1.mssql_unittest.v42.v2beta1.json | 1969 ++++++++++++++++- .../v0alpha1.mysql_fakedata.v42.v2alpha1.json | 227 +- .../v0alpha1.mysql_fakedata.v42.v2beta1.json | 227 +- .../v0alpha1.mysql_unittest.v42.v2alpha1.json | 1841 ++++++++++++++- .../v0alpha1.mysql_unittest.v42.v2beta1.json | 1841 ++++++++++++++- .../v0alpha1.opentsdb.v42.v2alpha1.json | 118 + .../v0alpha1.opentsdb.v42.v2beta1.json | 118 + .../v0alpha1.opentsdb_v23.v42.v2alpha1.json | 118 + .../v0alpha1.opentsdb_v23.v42.v2beta1.json | 118 + ...alpha1.postgres_fakedata.v42.v2alpha1.json | 227 +- ...0alpha1.postgres_fakedata.v42.v2beta1.json | 227 +- ...alpha1.postgres_unittest.v42.v2alpha1.json | 1817 ++++++++++++++- ...0alpha1.postgres_unittest.v42.v2beta1.json | 1817 ++++++++++++++- ...lpha1.testdata-datalinks.v42.v2alpha1.json | 116 + ...alpha1.testdata-datalinks.v42.v2beta1.json | 116 + ...sted-variables-drilldown.v42.v2alpha1.json | 121 +- ...ested-variables-drilldown.v42.v2beta1.json | 121 +- ...estdata-nested-variables.v42.v2alpha1.json | 113 +- ...testdata-nested-variables.v42.v2beta1.json | 113 +- ...stdata-variables-textbox.v42.v2alpha1.json | 59 + ...estdata-variables-textbox.v42.v2beta1.json | 59 + ...at-update-on-time-change.v42.v2alpha1.json | 61 + ...hat-update-on-time-change.v42.v2beta1.json | 61 + .../v0alpha1.migrations.v42.v2alpha1.json | 385 +++- .../v0alpha1.migrations.v42.v2beta1.json | 385 +++- .../v0alpha1.auto_decimals.v42.v2alpha1.json | 771 +++++++ .../v0alpha1.auto_decimals.v42.v2beta1.json | 771 +++++++ .../v0alpha1.lazy_loading.v42.v2alpha1.json | 1285 ++++++++++- .../v0alpha1.lazy_loading.v42.v2beta1.json | 1285 ++++++++++- ...ha1.panels_without_title.v42.v2alpha1.json | 372 +++- ...pha1.panels_without_title.v42.v2beta1.json | 372 +++- ...raph-gradient-area-fills.v42.v2alpha1.json | 252 ++- ...graph-gradient-area-fills.v42.v2beta1.json | 252 ++- ...a1.graph-shared-tooltips.v42.v2alpha1.json | 118 + ...ha1.graph-shared-tooltips.v42.v2beta1.json | 118 + ...lpha1.graph-time-regions.v42.v2alpha1.json | 420 +++- ...alpha1.graph-time-regions.v42.v2beta1.json | 420 +++- .../v0alpha1.graph_tests.v42.v2alpha1.json | 949 +++++++- .../v0alpha1.graph_tests.v42.v2beta1.json | 949 +++++++- .../v0alpha1.graph_y_axis.v42.v2alpha1.json | 533 +++++ .../v0alpha1.graph_y_axis.v42.v2beta1.json | 533 +++++ .../v0alpha1.panel-library.v42.v2alpha1.json | 59 + .../v0alpha1.panel-library.v42.v2beta1.json | 59 + .../v0alpha1.table_tests.v42.v2alpha1.json | 402 +++- .../v0alpha1.table_tests.v42.v2beta1.json | 402 +++- ...a1.timeseries-thresholds.v42.v2alpha1.json | 505 +++++ ...ha1.timeseries-thresholds.v42.v2beta1.json | 505 +++++ ..._queries_and_annotations.v42.v2alpha1.json | 767 ++++++- ...w_queries_and_annotations.v42.v2beta1.json | 767 ++++++- ...alpha1.time_zone_support.v42.v2alpha1.json | 388 ++++ ...0alpha1.time_zone_support.v42.v2beta1.json | 388 ++++ .../v1beta1.angular-migrations.v0alpha1.json | 1310 +++++++++++ .../v1beta1.angular-migrations.v2alpha1.json | 1861 ++++++++++++++++ .../v1beta1.angular-migrations.v2beta1.json | 1912 ++++++++++++++++ .../v1beta1.panel-conversions.v2alpha1.json | 7 +- .../v1beta1.panel-conversions.v2beta1.json | 7 +- .../conversion/v1beta1_to_v2alpha1.go | 62 +- .../pkg/migration/frontend_defaults.go | 51 +- .../schemaversion/migration_utils.go | 68 + kinds/dashboard/dashboard_kind.cue | 4 + .../raw/dashboard/x/dashboard_types.gen.ts | 5 + pkg/kinds/dashboard/dashboard_spec_gen.go | 3 + .../serialization/angularMigration.test.ts | 171 +- .../serialization/angularMigration.ts | 74 + .../serialization/layoutSerializers/utils.ts | 19 +- .../dashboard/api/ResponseTransformers.ts | 69 +- .../api/ResponseTransformersToBackend.test.ts | 262 +-- .../DashboardMigratorSingleVersion.test.ts | 1 - .../state/DashboardMigratorToBackend.test.ts | 1 - 142 files changed, 37679 insertions(+), 653 deletions(-) create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.angular-migrations.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.angular-migrations.v0alpha1.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.angular-migrations.v2alpha1.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.angular-migrations.v2beta1.json diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue index 4a255a57ba6..8338c9e13c5 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue @@ -706,6 +706,10 @@ lineage: schemas: [{ // Field options allow you to change how the data is displayed in your visualizations. fieldConfig?: #FieldConfigSource + + // When a panel is migrated from a previous version (Angular to React), this field is set to the original panel type. + // This is used to determine the original panel type when migrating to a new version so the plugin migration can be applied. + autoMigrateFrom?: string } @cuetsy(kind="interface") @grafana(TSVeneer="type") @grafanamaturity(NeedsExpertReview) // The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results. diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue index 4a255a57ba6..8338c9e13c5 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue @@ -706,6 +706,10 @@ lineage: schemas: [{ // Field options allow you to change how the data is displayed in your visualizations. fieldConfig?: #FieldConfigSource + + // When a panel is migrated from a previous version (Angular to React), this field is set to the original panel type. + // This is used to determine the original panel type when migrating to a new version so the plugin migration can be applied. + autoMigrateFrom?: string } @cuetsy(kind="interface") @grafana(TSVeneer="type") @grafanamaturity(NeedsExpertReview) // The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results. diff --git a/apps/dashboard/pkg/migration/README.md b/apps/dashboard/pkg/migration/README.md index ab938e31f66..6fd2069cce9 100644 --- a/apps/dashboard/pkg/migration/README.md +++ b/apps/dashboard/pkg/migration/README.md @@ -53,6 +53,7 @@ v0alpha1 (Legacy JSON) → v1beta1 (Migrated JSON) → v2alpha1/v2beta1 (Structu - 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: @@ -61,6 +62,76 @@ v0alpha1 (Legacy JSON) → v1beta1 (Migrated JSON) → v2alpha1/v2beta1 (Structu - 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: diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.angular-migrations.json b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.angular-migrations.json new file mode 100644 index 00000000000..3d496f51731 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.angular-migrations.json @@ -0,0 +1,1310 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "angular-migrations-test" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate graph panel (TRUE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateGraphPanel=true" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate graph panel (FALSE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateGraphPanel=false" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate table (old) panel (TRUE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateTablePanel=true" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate table (old) panel (FALSE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateTablePanel=false" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate piechart panel (TRUE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigratePiechartPanel=true" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate piechart panel (FALSE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigratePiechartPanel=false" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate worldmap panel (TRUE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateWorldmapPanel=true" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate worldmap panel (FALSE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateWorldmapPanel=false" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate stat panel (TRUE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateStatPanel=true" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate stat panel (FALSE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateStatPanel=false" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate (TRUE)", + "tooltip": "", + "type": "link", + "url": "/d/cdd412c4/?__feature.autoMigrateOldPanels=true" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate (FALSE)", + "tooltip": "", + "type": "link", + "url": "/d/cdd412c4/?__feature.autoMigrateOldPanels=false" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Disable angular (TRUE)", + "tooltip": "", + "type": "link", + "url": "/d/cdd412c4/?__feature.disableAngular=true" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Disable angular (FALSE)", + "tooltip": "", + "type": "link", + "url": "/d/cdd412c4/?__feature.disableAngular=false" + } + ], + "liveNow": false, + "panels": [ + { + "aliasColors": {}, + "type": "graph", + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 16, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "percentage": false, + "pluginVersion": "11.0.0-pre", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 3 + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Flot graph", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 11, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 6, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", + "mode": "markdown" + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "aliasColors": {}, + "autoMigrateFrom": "graph", + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 16, + "x": 0, + "y": 11 + }, + "hiddenSeries": false, + "id": 28, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "percentage": false, + "pluginVersion": "11.0.0-pre", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ],\n \"custom\": {\n \"customStat\": 10\n }\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 3600000\n }\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {\n \"pod\": \"A-pod\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1727107111901,\n 1727110711901,\n 1727114311901,\n 1727117911901,\n 1727121511901,\n 1727125111901\n ],\n [\n 1.907286825122581,\n 2.260951647569786,\n 1.887442338051216,\n 2.1526144400893514,\n 1.7287721375237766,\n 1.7262902137793208\n ]\n ]\n }\n },\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ],\n \"custom\": {\n \"customStat\": 10\n }\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 3600000\n }\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {\n \"pod\": \"A-pod1\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1727107111901,\n 1727110711901,\n 1727114311901,\n 1727117911901,\n 1727121511901,\n 1727125111901\n ],\n [\n 1.907286825122581,\n 1.589539045095202,\n 1.5914283506847613,\n 1.8976990616650726,\n 1.758223085999124,\n 2.2294649594813816\n ]\n ]\n }\n },\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ],\n \"custom\": {\n \"customStat\": 10\n }\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 3600000\n }\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {\n \"pod\": \"A-pod2\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1727107111901,\n 1727110711901,\n 1727114311901,\n 1727117911901,\n 1727121511901,\n 1727125111901\n ],\n [\n 1.907286825122581,\n 2.0914263380328766,\n 1.8164545521094575,\n 1.621111084665713,\n 1.3902653996444705,\n 1.482803315949775\n ]\n ]\n }\n }\n]", + "refId": "A", + "scenarioId": "raw_frame" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Flot graph - x axis series mode", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "type": "barchart", + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:88", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:89", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 11, + "w": 8, + "x": 16, + "y": 11 + }, + "id": 29, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Bar chart panel\n", + "mode": "markdown" + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "aliasColors": {}, + "autoMigrateFrom": "graph", + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": { + "default": false, + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 16, + "x": 0, + "y": 22 + }, + "hiddenSeries": false, + "id": 32, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "percentage": false, + "pluginVersion": "11.3.0-pre", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 28, + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Flot graph - x axis series mode (with legend calcs)", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "type": "bargauge", + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:88", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:89", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "autoMigrateFrom": "graph", + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 16, + "x": 0, + "y": 22 + }, + "hiddenSeries": false, + "id": 30, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "percentage": false, + "pluginVersion": "11.0.0-pre", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 3 + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Flot graph - x axis histogram mode", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "type": "histogram", + "xaxis": { + "mode": "histogram", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:193", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:194", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 11, + "w": 8, + "x": 16, + "y": 22 + }, + "id": 33, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Bar gauge panel\n", + "mode": "markdown" + }, + "pluginVersion": "11.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 11, + "w": 8, + "x": 16, + "y": 22 + }, + "id": 31, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Histogram panel\n", + "mode": "markdown" + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "autoMigrateFrom": "table-old", + "columns": [], + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fontSize": "100%", + "gridPos": { + "h": 10, + "w": 16, + "x": 0, + "y": 33 + }, + "id": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "showRowNums": false + }, + "pluginVersion": "9.5.0-pre", + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "right", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "random_walk_table" + } + ], + "title": "Table (old)", + "transform": "table", + "transformations": [ + { + "id": "merge", + "options": { + "reducers": [] + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 16, + "y": 33 + }, + "id": 7, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Table (old) \u003e\u003e Table\n\nKnown issues:\n* wrapping text\n* style changes", + "mode": "markdown" + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "autoMigrateFrom": "grafana-singlestat-panel", + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": { + "type": "grafana-testdata-datasource" + }, + "format": "areaF2", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 43 + }, + "id": 9, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "postfix": "b", + "postfixFontSize": "50%", + "prefix": "a", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "thresholds": "", + "title": "grafana-singlestat-panel", + "type": "stat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 43 + }, + "id": 23, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "singlestat (old, internal. Migrated if schema \u003c 28)", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 43 + }, + "id": 10, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Singlestat \u003e\u003e Stat\n\nKnown issues:\n* limited options", + "mode": "markdown" + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "autoMigrateFrom": "grafana-piechart-panel", + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 10, + "w": 16, + "x": 0, + "y": 51 + }, + "id": 24, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.5.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "random_walk_table" + } + ], + "title": "grafana-piechart-panel", + "transformations": [ + { + "id": "merge", + "options": { + "reducers": [] + } + } + ], + "type": "piechart" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 16, + "y": 51 + }, + "id": 25, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# grafana-piechart-panel \u003e\u003e piechart\n\nKnown issues:\n* TBD", + "mode": "markdown" + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "autoMigrateFrom": "grafana-worldmap-panel", + "circleMaxSize": 30, + "circleMinSize": 2, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": { + "type": "grafana-testdata-datasource" + }, + "decimals": 0, + "esMetric": "Count", + "gridPos": { + "h": 10, + "w": 16, + "x": 0, + "y": 61 + }, + "hideEmpty": false, + "hideZero": false, + "id": 26, + "initialZoom": 1, + "locationData": "countries", + "mapCenter": "(0°, 0°)", + "mapCenterLatitude": 0, + "mapCenterLongitude": 0, + "maxDataPoints": 1, + "mouseWheelZoom": false, + "options": { + "basemap": { + "name": "Basemap", + "type": "default" + }, + "controls": { + "mouseWheelZoom": false, + "showAttribution": true, + "showDebug": false, + "showMeasure": false, + "showScale": false, + "showZoom": true + }, + "layers": [ + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "dark-green" + }, + "opacity": 0.4, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 5, + "max": 30, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "location": { + "gazetteer": "public/gazetteer/countries.json", + "mode": "lookup" + }, + "name": "Layer 0", + "tooltip": true, + "type": "markers" + } + ], + "tooltip": { + "mode": "details" + }, + "view": { + "allLayers": true, + "id": "zero", + "lat": 0, + "lon": 0, + "zoom": 1 + } + }, + "pluginVersion": "10.4.0-pre", + "showLegend": true, + "stickyLabels": false, + "tableQueryOptions": { + "geohashField": "geohash", + "latitudeField": "latitude", + "longitudeField": "longitude", + "metricField": "metric", + "queryType": "geohash" + }, + "targets": [ + { + "csvFileName": "flight_info_by_state.csv", + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_file" + } + ], + "thresholds": "0,10", + "title": "grafana-worldmap-panel", + "transformations": [ + { + "id": "merge", + "options": { + "reducers": [] + } + }, + { + "id": "reduce", + "options": { + "reducers": [ + "sum" + ] + } + } + ], + "type": "geomap", + "unitPlural": "", + "unitSingle": "", + "valueName": "total" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 16, + "y": 61 + }, + "id": 27, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# grafana-worldmap-panel \u003e\u003e geomap\n\nKnown issues:\n* TBD", + "mode": "markdown" + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [ + "gdev", + "migrations", + "angular" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Devenv - Panel migrations", + "uid": "cdd412c4", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v0alpha1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2alpha1.json index 5fdde6e46be..669e2e742a6 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2alpha1.json @@ -66,7 +66,29 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "align": "auto", + "thresholds": [ + "20", + "30" + ] + }, + { + "align": "auto", + "thresholds": [ + "200", + "300" + ] + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -110,7 +132,22 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "align": "auto", + "thresholds": [ + "50", + "75" + ] + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -154,7 +191,22 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "styles": [ + { + "thresholds": [ + "5", + "10", + "15" + ] + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2beta1.json index b2eb0336cf2..22a36b375a0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v10.table_thresholds.v42.v2beta1.json @@ -69,7 +69,29 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "align": "auto", + "thresholds": [ + "20", + "30" + ] + }, + { + "align": "auto", + "thresholds": [ + "200", + "300" + ] + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -115,7 +137,22 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "align": "auto", + "thresholds": [ + "50", + "75" + ] + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -161,7 +198,22 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "styles": [ + { + "thresholds": [ + "5", + "10", + "15" + ] + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2alpha1.json index 7185cc96f69..b9007aeab43 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2alpha1.json @@ -66,7 +66,18 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "yAxes": [ + { + "show": true + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -110,7 +121,20 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2beta1.json index 2a0200eb4cb..75255794c5c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v11.no-op-migration.v42.v2beta1.json @@ -69,7 +69,18 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "yAxes": [ + { + "show": true + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -115,7 +126,20 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2alpha1.json index 2d55803abbe..091b4103636 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2alpha1.json @@ -66,7 +66,20 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "grid": { + "threshold1": 200, + "threshold1Color": "yellow", + "threshold2": 400, + "threshold2Color": "red", + "thresholdLine": true + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -110,7 +123,20 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "grid": { + "threshold1": 100, + "threshold1Color": "green", + "threshold2": 300, + "threshold2Color": "blue", + "thresholdLine": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -154,7 +180,18 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "grid": { + "threshold1": 150, + "threshold1Color": "orange", + "thresholdLine": true + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -198,7 +235,25 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "grid": { + "threshold1": 200, + "threshold1Color": "yellow", + "thresholdLine": false + }, + "thresholds": [ + { + "color": "purple", + "colorMode": "custom", + "value": 50 + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -242,7 +297,12 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2beta1.json index 97fc1bfca44..0fdbbd1990b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.graph_thresholds.v42.v2beta1.json @@ -69,7 +69,20 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "grid": { + "threshold1": 200, + "threshold1Color": "yellow", + "threshold2": 400, + "threshold2Color": "red", + "thresholdLine": true + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -115,7 +128,20 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "grid": { + "threshold1": 100, + "threshold1Color": "green", + "threshold2": 300, + "threshold2Color": "blue", + "thresholdLine": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -161,7 +187,18 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "grid": { + "threshold1": 150, + "threshold1Color": "orange", + "thresholdLine": true + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -207,7 +244,25 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "grid": { + "threshold1": 200, + "threshold1Color": "yellow", + "thresholdLine": false + }, + "thresholds": [ + { + "color": "purple", + "colorMode": "custom", + "value": 50 + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -253,7 +308,12 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2alpha1.json index 818011bba5b..b2673944cb7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2alpha1.json @@ -72,7 +72,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2beta1.json index 3c12cff0619..1b8e98bafc0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v13.minimal_graph_config.v42.v2beta1.json @@ -75,7 +75,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2alpha1.json index 4c9617a521e..5a00a12baa3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2alpha1.json @@ -68,7 +68,18 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "yAxes": [ + { + "show": true + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -114,7 +125,20 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "yAxes": [ + { + "max": 100, + "min": 0, + "show": true + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2beta1.json index bf07de3de24..9251f743242 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v14.shared_crosshair_to_graph_tooltip.v42.v2beta1.json @@ -71,7 +71,18 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "yAxes": [ + { + "show": true + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -119,7 +130,20 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "yAxes": [ + { + "max": 100, + "min": 0, + "show": true + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2alpha1.json index cd1daaf4806..551348d7852 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2alpha1.json @@ -83,7 +83,18 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "yAxes": [ + { + "show": true + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -127,7 +138,20 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2beta1.json index 63a51a69c96..c6ab962de2f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.no-op-migration.v42.v2beta1.json @@ -87,7 +87,18 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "yAxes": [ + { + "show": true + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -133,7 +144,20 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json index 13e2bd96197..7229c6df268 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json @@ -66,7 +66,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -154,7 +159,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -198,7 +208,12 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -254,7 +269,14 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "height": 200 + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -282,7 +304,14 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "height": 200 + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json index bbd59bf1a07..efdbb91f745 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json @@ -69,7 +69,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -161,7 +166,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -207,7 +217,12 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -265,7 +280,14 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "height": 200 + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -294,7 +316,14 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "height": 200 + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2alpha1.json index 05de2de2aa4..ecc7830b8fa 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2alpha1.json @@ -66,7 +66,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -110,7 +115,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -154,7 +164,12 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -242,7 +257,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -374,7 +394,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -462,7 +487,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2beta1.json index 778a653435a..ce68572f0d2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v17.minspan_to_maxperrow.v42.v2beta1.json @@ -69,7 +69,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -115,7 +120,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -161,7 +171,12 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -253,7 +268,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -391,7 +411,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -483,7 +508,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2alpha1.json index d524005a101..55f7729fc1a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2alpha1.json @@ -288,6 +288,10 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + }, "legend": { "show": true, "showLegend": true diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2beta1.json index b80c575053c..758bdfad09e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v18.gauge_options.v42.v2beta1.json @@ -299,6 +299,10 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + }, "legend": { "show": true, "showLegend": true diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2alpha1.json index 3d78d0167b3..596bd229b79 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2alpha1.json @@ -71,7 +71,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -312,7 +317,12 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2beta1.json index 2842123f75e..1302e17c7f3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v19.panel_links.v42.v2beta1.json @@ -74,7 +74,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -325,7 +330,12 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json index b6b4f83cdf7..57b7b24ed56 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json @@ -68,7 +68,20 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graphite", + "originalOptions": { + "grid": { + "max": 100, + "min": 0 + }, + "legend": true, + "y2_format": "short", + "y_format": "percent" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -114,7 +127,18 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "grid": { + "min": 0 + }, + "legend": false, + "y_format": "bytes" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -204,7 +228,15 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graphite", + "originalOptions": { + "legend": true, + "y2_format": "Bps" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json index 0b9d3bacd35..0143563e75b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json @@ -71,7 +71,20 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graphite", + "originalOptions": { + "grid": { + "max": 100, + "min": 0 + }, + "legend": true, + "y2_format": "short", + "y_format": "percent" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -119,7 +132,18 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "grid": { + "min": 0 + }, + "legend": false, + "y_format": "bytes" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -213,7 +237,15 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graphite", + "originalOptions": { + "legend": true, + "y2_format": "Bps" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2alpha1.json index 170e3c6ccad..a93d077fef3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2alpha1.json @@ -181,6 +181,10 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + }, "dataLinks": [ { "title": "Graph Data Link", diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2beta1.json index b87e2c2fb9e..6244177502b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v21.data_links_series_to_field.v42.v2beta1.json @@ -188,6 +188,10 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + }, "dataLinks": [ { "title": "Graph Data Link", diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2alpha1.json index 28eefba141b..10c570651e5 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2alpha1.json @@ -66,7 +66,25 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "align": "auto", + "pattern": "Time", + "type": "number" + }, + { + "align": "auto", + "pattern": "Value", + "type": "string" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2beta1.json index d9de4392f97..8c7d86f1d86 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v22.table_panel_align.v42.v2beta1.json @@ -69,7 +69,25 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "align": "auto", + "pattern": "Time", + "type": "number" + }, + { + "align": "auto", + "pattern": "Value", + "type": "string" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json index 206e53baee2..4734298869d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json @@ -81,7 +81,29 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "legend": true, + "styles": [ + { + "colors": [ + "red", + "yellow", + "green" + ], + "pattern": "/.*/", + "thresholds": [ + "10", + "20", + "30" + ] + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -125,7 +147,31 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "align": "center", + "pattern": "/.*/" + }, + { + "align": "left", + "pattern": "LeftColumn" + }, + { + "align": "right", + "pattern": "RightColumn" + }, + { + "align": "auto", + "pattern": "AutoColumn" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -169,7 +215,35 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "pattern": "/.*/", + "unit": "short" + }, + { + "alias": "Exact Match", + "pattern": "ExactColumnName" + }, + { + "alias": "Regex Match", + "pattern": "/Regex.*Pattern/" + }, + { + "alias": "Start Pattern", + "pattern": "/^Start/" + }, + { + "alias": "End Pattern", + "pattern": "/End$/" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -213,7 +287,37 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "pattern": "/.*/", + "unit": "short" + }, + { + "link": true, + "linkTargetBlank": true, + "linkTooltip": "Click to view details", + "linkUrl": "http://example.com/with-tooltip", + "pattern": "LinkWithTooltip" + }, + { + "link": true, + "linkTargetBlank": false, + "linkUrl": "http://example.com/no-tooltip", + "pattern": "LinkWithoutTooltip" + }, + { + "link": true, + "linkUrl": "http://example.com/minimal", + "pattern": "LinkMinimal" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -257,7 +361,37 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "pattern": "/.*/", + "unit": "short" + }, + { + "alias": "ISO Date", + "dateFormat": "YYYY-MM-DD", + "pattern": "DateISO", + "type": "date" + }, + { + "alias": "Full DateTime", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "DateTime", + "type": "date" + }, + { + "alias": "Time Only", + "dateFormat": "HH:mm:ss", + "pattern": "TimeOnly", + "type": "date" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -389,7 +523,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -433,7 +572,12 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -477,7 +621,57 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "align": "center", + "colorMode": "cell", + "colors": [ + "green", + "yellow", + "red" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [ + "100", + "500" + ], + "unit": "bytes" + }, + { + "alias": "Current Status", + "align": "left", + "colorMode": "value", + "decimals": 0, + "pattern": "Status", + "unit": "short" + }, + { + "colorMode": "row", + "link": true, + "linkTargetBlank": true, + "linkTooltip": "View error details", + "linkUrl": "http://example.com/errors", + "pattern": "/Error.*/" + }, + { + "alias": "Timestamp", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "pattern": "Hidden", + "type": "hidden" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -521,7 +715,47 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [ + { + "text": "Average", + "value": "avg" + }, + { + "text": "Maximum", + "value": "max" + }, + { + "text": "Minimum", + "value": "min" + }, + { + "text": "Total", + "value": "total" + }, + { + "text": "Current", + "value": "current" + }, + { + "text": "Count", + "value": "count" + } + ], + "styles": [ + { + "decimals": 1, + "pattern": "/.*/", + "unit": "percent" + } + ], + "transform": "timeseries_aggregations" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -565,7 +799,20 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "pattern": "/.*/", + "unit": "short" + } + ], + "transform": "timeseries_to_rows" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -609,7 +856,20 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "pattern": "/.*/", + "unit": "bytes" + } + ], + "transform": "timeseries_to_columns" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -653,7 +913,20 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "align": "auto", + "pattern": "/.*/" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -712,7 +985,20 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "pattern": "/.*/", + "unit": "short" + } + ], + "transform": "timeseries_to_rows" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -756,7 +1042,29 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "colors": [ + "green", + "yellow", + "orange", + "red" + ], + "pattern": "/.*/", + "thresholds": [ + 10, + "20", + 30.5 + ] + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -800,7 +1108,31 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "colorMode": "cell", + "pattern": "/.*/" + }, + { + "colorMode": "cell", + "pattern": "CellColumn" + }, + { + "colorMode": "row", + "pattern": "RowColumn" + }, + { + "colorMode": "value", + "pattern": "ValueColumn" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json index 9eeeb11b247..57979e9f551 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json @@ -85,7 +85,29 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "legend": true, + "styles": [ + { + "colors": [ + "red", + "yellow", + "green" + ], + "pattern": "/.*/", + "thresholds": [ + "10", + "20", + "30" + ] + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -131,7 +153,31 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "align": "center", + "pattern": "/.*/" + }, + { + "align": "left", + "pattern": "LeftColumn" + }, + { + "align": "right", + "pattern": "RightColumn" + }, + { + "align": "auto", + "pattern": "AutoColumn" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -177,7 +223,35 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "pattern": "/.*/", + "unit": "short" + }, + { + "alias": "Exact Match", + "pattern": "ExactColumnName" + }, + { + "alias": "Regex Match", + "pattern": "/Regex.*Pattern/" + }, + { + "alias": "Start Pattern", + "pattern": "/^Start/" + }, + { + "alias": "End Pattern", + "pattern": "/End$/" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -223,7 +297,37 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "pattern": "/.*/", + "unit": "short" + }, + { + "link": true, + "linkTargetBlank": true, + "linkTooltip": "Click to view details", + "linkUrl": "http://example.com/with-tooltip", + "pattern": "LinkWithTooltip" + }, + { + "link": true, + "linkTargetBlank": false, + "linkUrl": "http://example.com/no-tooltip", + "pattern": "LinkWithoutTooltip" + }, + { + "link": true, + "linkUrl": "http://example.com/minimal", + "pattern": "LinkMinimal" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -269,7 +373,37 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "pattern": "/.*/", + "unit": "short" + }, + { + "alias": "ISO Date", + "dateFormat": "YYYY-MM-DD", + "pattern": "DateISO", + "type": "date" + }, + { + "alias": "Full DateTime", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "DateTime", + "type": "date" + }, + { + "alias": "Time Only", + "dateFormat": "HH:mm:ss", + "pattern": "TimeOnly", + "type": "date" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -407,7 +541,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -453,7 +592,12 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -499,7 +643,57 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "align": "center", + "colorMode": "cell", + "colors": [ + "green", + "yellow", + "red" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [ + "100", + "500" + ], + "unit": "bytes" + }, + { + "alias": "Current Status", + "align": "left", + "colorMode": "value", + "decimals": 0, + "pattern": "Status", + "unit": "short" + }, + { + "colorMode": "row", + "link": true, + "linkTargetBlank": true, + "linkTooltip": "View error details", + "linkUrl": "http://example.com/errors", + "pattern": "/Error.*/" + }, + { + "alias": "Timestamp", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "pattern": "Hidden", + "type": "hidden" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -545,7 +739,47 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [ + { + "text": "Average", + "value": "avg" + }, + { + "text": "Maximum", + "value": "max" + }, + { + "text": "Minimum", + "value": "min" + }, + { + "text": "Total", + "value": "total" + }, + { + "text": "Current", + "value": "current" + }, + { + "text": "Count", + "value": "count" + } + ], + "styles": [ + { + "decimals": 1, + "pattern": "/.*/", + "unit": "percent" + } + ], + "transform": "timeseries_aggregations" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -591,7 +825,20 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "pattern": "/.*/", + "unit": "short" + } + ], + "transform": "timeseries_to_rows" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -637,7 +884,20 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "pattern": "/.*/", + "unit": "bytes" + } + ], + "transform": "timeseries_to_columns" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -683,7 +943,20 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "align": "auto", + "pattern": "/.*/" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -744,7 +1017,20 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "pattern": "/.*/", + "unit": "short" + } + ], + "transform": "timeseries_to_rows" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -790,7 +1076,29 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "colors": [ + "green", + "yellow", + "orange", + "red" + ], + "pattern": "/.*/", + "thresholds": [ + 10, + "20", + 30.5 + ] + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -836,7 +1144,31 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "styles": [ + { + "colorMode": "cell", + "pattern": "/.*/" + }, + { + "colorMode": "cell", + "pattern": "CellColumn" + }, + { + "colorMode": "row", + "pattern": "RowColumn" + }, + { + "colorMode": "value", + "pattern": "ValueColumn" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2alpha1.json index 35c94ad87bc..618d1193a1c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2alpha1.json @@ -123,7 +123,18 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "yAxes": [ + { + "show": true + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2beta1.json index 1cdd7407a2d..7fd6cdec538 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v25.no-op-migration.v42.v2beta1.json @@ -130,7 +130,18 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "yAxes": [ + { + "show": true + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2alpha1.json index 1c1b3d0d39b..bab8c53ac6a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2alpha1.json @@ -66,7 +66,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -94,7 +99,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2beta1.json index b8596415db8..79dab4989d1 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v27.repeated_panels_and_constant_variable.v42.v2beta1.json @@ -69,7 +69,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -98,7 +103,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2alpha1.json index 78df8e5bf3a..14e59bfc9bc 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2alpha1.json @@ -81,7 +81,24 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colors": [ + "#FF0000", + "green", + "orange" + ], + "grid": { + "max": 10, + "min": 1 + }, + "legend": true, + "thresholds": "10,20,30" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -125,7 +142,28 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colors": [ + "#FF0000", + "green", + "orange" + ], + "gauge": { + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "grid": { + "max": 10, + "min": 1 + }, + "thresholds": "10,20,30" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -184,7 +222,47 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colors": [ + "#FF0000", + "green", + "orange" + ], + "grid": { + "max": 10, + "min": 1 + }, + "legend": true, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + } + ], + "thresholds": "10,20,30", + "valueMaps": [ + { + "op": "=", + "text": "test", + "value": "20" + }, + { + "op": "=", + "text": "test1", + "value": "30" + }, + { + "op": "=", + "text": "50", + "value": "40" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2beta1.json index f9b06083b63..4f376187bba 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_and_variable_properties.v42.v2beta1.json @@ -85,7 +85,24 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colors": [ + "#FF0000", + "green", + "orange" + ], + "grid": { + "max": 10, + "min": 1 + }, + "legend": true, + "thresholds": "10,20,30" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -131,7 +148,28 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colors": [ + "#FF0000", + "green", + "orange" + ], + "gauge": { + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "grid": { + "max": 10, + "min": 1 + }, + "thresholds": "10,20,30" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -193,7 +231,47 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colors": [ + "#FF0000", + "green", + "orange" + ], + "grid": { + "max": 10, + "min": 1 + }, + "legend": true, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + } + ], + "thresholds": "10,20,30", + "valueMaps": [ + { + "op": "=", + "text": "test", + "value": "20" + }, + { + "op": "=", + "text": "test1", + "value": "30" + }, + { + "op": "=", + "text": "50", + "value": "40" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2alpha1.json index 7fa4e5ae944..195ceebb6cc 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2alpha1.json @@ -81,7 +81,24 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colors": [ + "#FF0000", + "green", + "orange" + ], + "grid": { + "max": 10, + "min": 1 + }, + "legend": true, + "thresholds": "10,20,30" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -140,7 +157,24 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colors": [ + "#FF0000", + "green", + "orange" + ], + "grid": { + "max": 10, + "min": 1 + }, + "legend": true, + "thresholds": "" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -184,7 +218,28 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colors": [ + "#FF0000", + "green", + "orange" + ], + "gauge": { + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "grid": { + "max": 10, + "min": 1 + }, + "thresholds": "10,20,30" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -243,7 +298,47 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colors": [ + "#FF0000", + "green", + "orange" + ], + "grid": { + "max": 10, + "min": 1 + }, + "legend": true, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + } + ], + "thresholds": "10,20,30", + "valueMaps": [ + { + "op": "=", + "text": "test", + "value": "20" + }, + { + "op": "=", + "text": "test1", + "value": "30" + }, + { + "op": "=", + "text": "50", + "value": "40" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -289,7 +384,68 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-singlestat-panel", + "originalOptions": { + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "format": "areaF2", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "b", + "postfixFontSize": "50%", + "prefix": "a", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2beta1.json index 5aaff274c67..fb221254e90 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v28.singlestat_migration.v42.v2beta1.json @@ -85,7 +85,24 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colors": [ + "#FF0000", + "green", + "orange" + ], + "grid": { + "max": 10, + "min": 1 + }, + "legend": true, + "thresholds": "10,20,30" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -147,7 +164,24 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colors": [ + "#FF0000", + "green", + "orange" + ], + "grid": { + "max": 10, + "min": 1 + }, + "legend": true, + "thresholds": "" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -193,7 +227,28 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colors": [ + "#FF0000", + "green", + "orange" + ], + "gauge": { + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "grid": { + "max": 10, + "min": 1 + }, + "thresholds": "10,20,30" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -255,7 +310,47 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colors": [ + "#FF0000", + "green", + "orange" + ], + "grid": { + "max": 10, + "min": 1 + }, + "legend": true, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + } + ], + "thresholds": "10,20,30", + "valueMaps": [ + { + "op": "=", + "text": "test", + "value": "20" + }, + { + "op": "=", + "text": "test1", + "value": "30" + }, + { + "op": "=", + "text": "50", + "value": "40" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -303,7 +398,68 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-singlestat-panel", + "originalOptions": { + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "format": "areaF2", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "b", + "postfixFontSize": "50%", + "prefix": "a", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2alpha1.json index c657d8796c3..40e4072138c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2alpha1.json @@ -280,6 +280,10 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + }, "tooltip": { "mode": "single" } diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2beta1.json index be92e718d44..9f5630252bf 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v30.value_mappings_and_tooltip_options.v42.v2beta1.json @@ -289,6 +289,10 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + }, "tooltip": { "mode": "single" } diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2alpha1.json index 2042cc9aa49..369db4327b7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2alpha1.json @@ -144,7 +144,18 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "yAxes": [ + { + "show": true + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2beta1.json index 7a90cf0c2ac..1fd5d1638ec 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v32.no_op_migration.v42.v2beta1.json @@ -151,7 +151,18 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "yAxes": [ + { + "show": true + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2alpha1.json index 6043004b0eb..b21c071f750 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2alpha1.json @@ -345,7 +345,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2beta1.json index af689d56d45..c541c51dc42 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v33.panel_ds_name_to_ref.v42.v2beta1.json @@ -361,7 +361,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2alpha1.json index 62c7806854a..7dc1d5c7069 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2alpha1.json @@ -111,6 +111,10 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + }, "legend": { "displayMode": "list", "showLegend": false @@ -207,6 +211,10 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + }, "legend": { "displayMode": "list", "showLegend": false diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2beta1.json index 9e112233023..ba78f73005d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v37.legend_normalization.v42.v2beta1.json @@ -115,6 +115,10 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + }, "legend": { "displayMode": "list", "showLegend": false @@ -214,6 +218,10 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + }, "legend": { "displayMode": "list", "showLegend": false diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2alpha1.json index dd8087721a9..89dd54e3bf6 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2alpha1.json @@ -443,7 +443,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2beta1.json index 3a75ab8b887..546c73663d6 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.table_displaymode_comprehensive.v42.v2beta1.json @@ -455,7 +455,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2alpha1.json index f7ed9a945bf..92dce415c0d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2alpha1.json @@ -443,7 +443,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2beta1.json index f8eb9ef3616..7d89cc8bbdd 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v38.timeseries_table_display_mode.v42.v2beta1.json @@ -455,7 +455,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2alpha1.json index 368f059af62..8d382676f16 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2alpha1.json @@ -217,7 +217,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -396,7 +401,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2beta1.json index 99de7609d9a..fe92c7f9537 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v39.transform_timeseries_table.v42.v2beta1.json @@ -222,7 +222,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -406,7 +411,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2alpha1.json index 983b06ebf43..45615b7ae44 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2alpha1.json @@ -66,7 +66,12 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -110,7 +115,12 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -154,7 +164,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2beta1.json index 4e523705cb0..3a7e5420f11 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v4.no-op.v42.v2beta1.json @@ -69,7 +69,12 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -115,7 +120,12 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -161,7 +171,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2alpha1.json index f7d9a922468..e019782a68b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2alpha1.json @@ -94,7 +94,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [ diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2beta1.json index f5eaa04d6ab..f97c257c251 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v42.hidefrom_tooltip.v42.v2beta1.json @@ -97,7 +97,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [ diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2alpha1.json index d39da494ad0..a1fc7ad4752 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2alpha1.json @@ -66,7 +66,12 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -110,7 +115,12 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -154,7 +164,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2beta1.json index 4795fc07dc5..860ab47b3ea 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v5.no-op.v42.v2beta1.json @@ -69,7 +69,12 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -115,7 +120,12 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -161,7 +171,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2alpha1.json index 60ef3b2ec90..6ca93a6768a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2alpha1.json @@ -108,7 +108,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -154,7 +159,12 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2beta1.json index 3396e17655d..0fa353e9a30 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v6.pulldowns_and_templating.v42.v2beta1.json @@ -113,7 +113,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -161,7 +166,12 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2alpha1.json index 7b4432f77b5..1bdd08aecf5 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2alpha1.json @@ -119,7 +119,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -177,7 +182,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2beta1.json index 40d7695e10c..ed5b9515a9f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v8.influxdb_query.v42.v2beta1.json @@ -122,7 +122,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -182,7 +187,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2alpha1.json index e39200fdc8e..b69579db16b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2alpha1.json @@ -66,7 +66,12 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -110,7 +115,12 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -154,7 +164,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2beta1.json index 71ba1439de3..2f8f9c79a0b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v9.no-op.v42.v2beta1.json @@ -69,7 +69,12 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -115,7 +120,12 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -161,7 +171,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2alpha1.json index 0ed400e5163..d30cf519eb9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2alpha1.json @@ -164,6 +164,65 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2beta1.json index 4bb33313829..def63fe73f1 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-logs.v42.v2beta1.json @@ -170,6 +170,65 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2alpha1.json index 63037588772..7bd7146240e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2alpha1.json @@ -113,7 +113,78 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "annotate": { + "enable": false + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 2, + "grid": {}, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "resolution": 100, + "scale": 1, + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "msResolution": false, + "query_as_alias": true, + "shared": false, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + }, + "zerofill": true + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2beta1.json index 19495974932..943bd4c0a7b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-influxdb/v0alpha1.influxdb-templated.v42.v2beta1.json @@ -117,7 +117,78 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "annotate": { + "enable": false + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 2, + "grid": {}, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "resolution": 100, + "scale": 1, + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "msResolution": false, + "query_as_alias": true, + "shared": false, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + }, + "zerofill": true + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2alpha1.json index d7e3a8f61b9..0f908ced2aa 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2alpha1.json @@ -129,6 +129,74 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "total avg": "#6ed0e0" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "total avg", + "fill": 0, + "pointradius": 3, + "points": true + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -178,7 +246,45 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -227,6 +333,65 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -296,6 +461,65 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2beta1.json index 2d66d6ca855..a346f009588 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_fakedata.v42.v2beta1.json @@ -138,6 +138,74 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "total avg": "#6ed0e0" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "total avg", + "fill": 0, + "pointradius": 3, + "points": true + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -189,7 +257,45 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -240,6 +346,65 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -312,6 +477,65 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2alpha1.json index 69c1c822020..747942f6dc3 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2alpha1.json @@ -147,7 +147,65 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -195,7 +253,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -243,7 +358,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -291,7 +463,69 @@ "kind": "bargauge", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -339,7 +573,69 @@ "kind": "bargauge", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -387,7 +683,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -435,7 +788,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": true, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -483,7 +898,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -531,7 +1006,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -579,7 +1116,38 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "string", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -627,7 +1195,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": true, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -675,7 +1303,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -723,7 +1414,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 100, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -771,7 +1525,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": true, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -819,7 +1636,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": true, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -867,7 +1747,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 50, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -915,7 +1858,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -963,7 +1969,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1011,7 +2079,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1059,7 +2187,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1107,7 +2297,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1156,7 +2408,45 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1205,7 +2495,45 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1254,7 +2582,45 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1303,7 +2669,45 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1351,7 +2755,65 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1399,7 +2861,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1447,7 +2966,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1514,7 +3095,79 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "MovingAverageValueOne", + "dashes": true, + "lines": false + }, + { + "alias": "MovingAverageValueTwo", + "dashes": true, + "lines": false, + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1562,7 +3215,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1629,7 +3344,79 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "MovingAverageValueOne", + "dashes": true, + "lines": false + }, + { + "alias": "MovingAverageValueTwo", + "dashes": true, + "lines": false, + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1677,7 +3464,65 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1725,7 +3570,65 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2beta1.json index 5ddf1d701a7..fa5c52ae871 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mssql/v0alpha1.mssql_unittest.v42.v2beta1.json @@ -159,7 +159,65 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -209,7 +267,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -259,7 +374,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -309,7 +481,69 @@ "group": "bargauge", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -359,7 +593,69 @@ "group": "bargauge", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -409,7 +705,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -459,7 +812,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": true, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -509,7 +924,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -559,7 +1034,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -609,7 +1146,38 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "string", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -659,7 +1227,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": true, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -709,7 +1337,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -759,7 +1450,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 100, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -809,7 +1563,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": true, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -859,7 +1676,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": true, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -909,7 +1789,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 50, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -959,7 +1902,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1009,7 +2015,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1059,7 +2127,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1109,7 +2237,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1159,7 +2349,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1210,7 +2462,45 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1261,7 +2551,45 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1312,7 +2640,45 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1363,7 +2729,45 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1413,7 +2817,65 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1463,7 +2925,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1513,7 +3032,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1583,7 +3164,79 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "MovingAverageValueOne", + "dashes": true, + "lines": false + }, + { + "alias": "MovingAverageValueTwo", + "dashes": true, + "lines": false, + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1633,7 +3286,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1703,7 +3418,79 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "MovingAverageValueOne", + "dashes": true, + "lines": false + }, + { + "alias": "MovingAverageValueTwo", + "dashes": true, + "lines": false, + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1753,7 +3540,65 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1803,7 +3648,65 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2alpha1.json index 6dc4619db1d..59f1c712177 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2alpha1.json @@ -128,6 +128,74 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "total avg": "#6ed0e0" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "total avg", + "fill": 0, + "pointradius": 3, + "points": true + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -179,6 +247,65 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -249,6 +376,65 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -299,7 +485,46 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "link": false, + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2beta1.json index ff02a8b9af0..3f782078e85 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_fakedata.v42.v2beta1.json @@ -137,6 +137,74 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "total avg": "#6ed0e0" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "total avg", + "fill": 0, + "pointradius": 3, + "points": true + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -190,6 +258,65 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -263,6 +390,65 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -315,7 +501,46 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "link": false, + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2alpha1.json index 631815cbc3f..ccfd35d981e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2alpha1.json @@ -147,7 +147,65 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -195,7 +253,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -243,7 +358,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -291,7 +463,69 @@ "kind": "bargauge", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -339,7 +573,69 @@ "kind": "bargauge", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -387,7 +683,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -435,7 +788,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": true, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -483,7 +898,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -531,7 +1006,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -579,7 +1116,38 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "string", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -627,7 +1195,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": true, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -675,7 +1303,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -723,7 +1414,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 100, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -771,7 +1525,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": true, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -819,7 +1636,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": true, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -867,7 +1747,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 50, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -915,7 +1858,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -963,7 +1969,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1011,7 +2079,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1060,7 +2188,45 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time_sec", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1109,7 +2275,45 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time_sec", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1158,7 +2362,45 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time_sec", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1207,7 +2449,45 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1255,7 +2535,65 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1303,7 +2641,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1351,7 +2746,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1399,7 +2856,79 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "MovingAverageValueOne", + "dashes": true, + "lines": false + }, + { + "alias": "MovingAverageValueTwo", + "dashes": true, + "lines": false, + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1447,7 +2976,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1495,7 +3086,79 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "MovingAverageValueOne", + "dashes": true, + "lines": false + }, + { + "alias": "MovingAverageValueTwo", + "dashes": true, + "lines": false, + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1543,7 +3206,65 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1591,7 +3312,65 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2beta1.json index 6ab6536924f..643298fb85c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-mysql/v0alpha1.mysql_unittest.v42.v2beta1.json @@ -159,7 +159,65 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -209,7 +267,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -259,7 +374,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -309,7 +481,69 @@ "group": "bargauge", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -359,7 +593,69 @@ "group": "bargauge", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -409,7 +705,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -459,7 +812,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": true, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -509,7 +924,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -559,7 +1034,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -609,7 +1146,38 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "string", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -659,7 +1227,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": true, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -709,7 +1337,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -759,7 +1450,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 100, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -809,7 +1563,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": true, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -859,7 +1676,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": true, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -909,7 +1789,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 50, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -959,7 +1902,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1009,7 +2015,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1059,7 +2127,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1110,7 +2238,45 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time_sec", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1161,7 +2327,45 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time_sec", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1212,7 +2416,45 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time_sec", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1263,7 +2505,45 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1313,7 +2593,65 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1363,7 +2701,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1413,7 +2808,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1463,7 +2920,79 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "MovingAverageValueOne", + "dashes": true, + "lines": false + }, + { + "alias": "MovingAverageValueTwo", + "dashes": true, + "lines": false, + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1513,7 +3042,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1563,7 +3154,79 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "MovingAverageValueOne", + "dashes": true, + "lines": false + }, + { + "alias": "MovingAverageValueTwo", + "dashes": true, + "lines": false, + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1613,7 +3276,65 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1663,7 +3384,65 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2alpha1.json index 7f3926d803e..893d135c144 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2alpha1.json @@ -68,6 +68,65 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -119,6 +178,65 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2beta1.json index 2959bf4ca8d..5086e9f987d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb.v42.v2beta1.json @@ -72,6 +72,65 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -125,6 +184,65 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2alpha1.json index 03a415d2b4d..deb7d353db8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2alpha1.json @@ -84,6 +84,65 @@ "spec": { "pluginVersion": "8.1.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { @@ -148,6 +207,65 @@ "spec": { "pluginVersion": "8.1.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2beta1.json index d3b15a72cf9..342f5a0a94c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-opentsdb/v0alpha1.opentsdb_v23.v42.v2beta1.json @@ -88,6 +88,65 @@ "version": "8.1.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { @@ -154,6 +213,65 @@ "version": "8.1.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2alpha1.json index 15b1ad08458..0e222d96742 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2alpha1.json @@ -170,6 +170,74 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "total avg": "#6ed0e0" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "total avg", + "fill": 0, + "pointradius": 3, + "points": true + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -221,6 +289,65 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -291,6 +418,65 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -341,7 +527,46 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "link": false, + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2beta1.json index d3572facb36..9ece61d256d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_fakedata.v42.v2beta1.json @@ -179,6 +179,74 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "total avg": "#6ed0e0" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "total avg", + "fill": 0, + "pointradius": 3, + "points": true + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -232,6 +300,65 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -305,6 +432,65 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -357,7 +543,46 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "link": false, + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2alpha1.json index 2ac583af244..a7bec316030 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2alpha1.json @@ -147,7 +147,65 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -195,7 +253,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -243,7 +358,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -291,7 +463,69 @@ "kind": "bargauge", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -339,7 +573,69 @@ "kind": "bargauge", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -387,7 +683,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -435,7 +788,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": true, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -483,7 +898,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -531,7 +1006,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -579,7 +1116,38 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 1, + "desc": false + }, + "styles": [ + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "string", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -627,7 +1195,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": true, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -675,7 +1303,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -723,7 +1414,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 100, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -771,7 +1525,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": true, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -819,7 +1636,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": true, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -867,7 +1747,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 50, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -915,7 +1858,70 @@ "kind": "histogram", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -963,7 +1969,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1011,7 +2079,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1060,7 +2188,45 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1109,7 +2275,45 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1158,7 +2362,45 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1207,7 +2449,45 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1255,7 +2535,65 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1303,7 +2641,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1351,7 +2746,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1399,7 +2856,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1447,7 +2964,69 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1495,7 +3074,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1543,7 +3182,65 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1591,7 +3288,65 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2beta1.json index 9a9dea7f6f3..ce0dd08d44d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/datasource-postgres/v0alpha1.postgres_unittest.v42.v2beta1.json @@ -159,7 +159,65 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -209,7 +267,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -259,7 +374,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -309,7 +481,69 @@ "group": "bargauge", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -359,7 +593,69 @@ "group": "bargauge", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -409,7 +705,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -459,7 +812,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": true, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -509,7 +924,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -559,7 +1034,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -609,7 +1146,38 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 1, + "desc": false + }, + "styles": [ + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "string", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -659,7 +1227,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": true, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -709,7 +1337,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -759,7 +1450,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 100, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -809,7 +1563,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": true, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -859,7 +1676,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": true, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -909,7 +1789,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 50, + "mode": "histogram", + "show": true, + "values": [ + "current" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -959,7 +1902,70 @@ "group": "histogram", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": false, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "buckets": 20, + "mode": "histogram", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1009,7 +2015,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1059,7 +2127,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1110,7 +2238,45 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1161,7 +2327,45 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1212,7 +2416,45 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1263,7 +2505,45 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1313,7 +2593,65 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1363,7 +2701,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1413,7 +2808,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1463,7 +2920,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1513,7 +3030,69 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1563,7 +3142,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1613,7 +3252,65 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1663,7 +3360,65 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json index 36419c8e892..6eb2f2493aa 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json @@ -86,6 +86,64 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [ { "targetBlank": false, @@ -354,6 +412,64 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [ { "targetBlank": false, diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json index 2450d74b2f2..22814d61a1e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json @@ -91,6 +91,64 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [ { "targetBlank": false, @@ -367,6 +425,64 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [ { "targetBlank": false, diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json index 710a4bc0280..a469d4020e5 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json @@ -62,6 +62,64 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -163,7 +221,68 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json index d93a4dca40f..0a46a76d48e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json @@ -67,6 +67,64 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -172,7 +230,68 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json index 1fb39d454b9..ecc156114f4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json @@ -167,6 +167,65 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -264,7 +323,59 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json index ed73f35670a..54d6e9efa1c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json @@ -174,6 +174,65 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -276,7 +335,59 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2alpha1.json index 3b34a0fd26e..81d61c2509b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2alpha1.json @@ -171,6 +171,65 @@ "spec": { "pluginVersion": "7.2.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2beta1.json index 6379e2a3191..01936dded6b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-textbox.v42.v2beta1.json @@ -177,6 +177,65 @@ "version": "7.2.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2alpha1.json index 67579b40d8c..7eb15579259 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2alpha1.json @@ -84,6 +84,67 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": true, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2beta1.json index d7c1db61b49..c89000daee7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-variables-that-update-on-time-change.v42.v2beta1.json @@ -89,6 +89,67 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": true, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json index 2ad3f371914..3c2a1a4f9d7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json @@ -123,6 +123,42 @@ "spec": { "pluginVersion": "9.5.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "right", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + }, "cellHeight": "sm", "footer": { "countRows": false, @@ -270,6 +306,10 @@ "spec": { "pluginVersion": "9.5.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-piechart-panel", + "originalOptions": {} + }, "legend": { "displayMode": "list", "placement": "bottom", @@ -402,6 +442,41 @@ "spec": { "pluginVersion": "10.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-worldmap-panel", + "originalOptions": { + "circleMaxSize": 30, + "circleMinSize": 2, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 0, + "esMetric": "Count", + "hideEmpty": false, + "hideZero": false, + "initialZoom": 1, + "locationData": "countries", + "mapCenter": "(0°, 0°)", + "mapCenterLatitude": 0, + "mapCenterLongitude": 0, + "mouseWheelZoom": false, + "showLegend": true, + "stickyLabels": false, + "tableQueryOptions": { + "geohashField": "geohash", + "latitudeField": "latitude", + "longitudeField": "longitude", + "metricField": "metric", + "queryType": "geohash" + }, + "thresholds": "0,10", + "unitPlural": "", + "unitSingle": "", + "valueName": "total" + } + }, "basemap": { "name": "Basemap", "type": "default" @@ -562,6 +637,69 @@ "spec": { "pluginVersion": "11.0.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:88", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:89", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true, "legend": { "calcs": [], @@ -667,6 +805,67 @@ "spec": { "pluginVersion": "11.0.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "histogram", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:193", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:194", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true, "legend": { "calcs": [], @@ -775,6 +974,70 @@ "spec": { "pluginVersion": "11.3.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:88", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:89", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true, "legend": { "calcs": [], @@ -880,6 +1143,65 @@ "spec": { "pluginVersion": "11.0.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true, "legend": { "calcs": [], @@ -1031,7 +1353,68 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-singlestat-panel", + "originalOptions": { + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "format": "areaF2", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "b", + "postfixFontSize": "50%", + "prefix": "a", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json index 5e8d1db63c1..c6a9186b9e0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json @@ -130,6 +130,42 @@ "version": "9.5.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "right", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + }, "cellHeight": "sm", "footer": { "countRows": false, @@ -283,6 +319,10 @@ "version": "9.5.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-piechart-panel", + "originalOptions": {} + }, "legend": { "displayMode": "list", "placement": "bottom", @@ -421,6 +461,41 @@ "version": "10.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-worldmap-panel", + "originalOptions": { + "circleMaxSize": 30, + "circleMinSize": 2, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 0, + "esMetric": "Count", + "hideEmpty": false, + "hideZero": false, + "initialZoom": 1, + "locationData": "countries", + "mapCenter": "(0°, 0°)", + "mapCenterLatitude": 0, + "mapCenterLongitude": 0, + "mouseWheelZoom": false, + "showLegend": true, + "stickyLabels": false, + "tableQueryOptions": { + "geohashField": "geohash", + "latitudeField": "latitude", + "longitudeField": "longitude", + "metricField": "metric", + "queryType": "geohash" + }, + "thresholds": "0,10", + "unitPlural": "", + "unitSingle": "", + "valueName": "total" + } + }, "basemap": { "name": "Basemap", "type": "default" @@ -587,6 +662,69 @@ "version": "11.0.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:88", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:89", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true, "legend": { "calcs": [], @@ -698,6 +836,67 @@ "version": "11.0.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "histogram", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:193", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:194", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true, "legend": { "calcs": [], @@ -811,6 +1010,70 @@ "version": "11.3.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:88", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:89", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true, "legend": { "calcs": [], @@ -922,6 +1185,65 @@ "version": "11.0.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true, "legend": { "calcs": [], @@ -1082,7 +1404,68 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-singlestat-panel", + "originalOptions": { + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "format": "areaF2", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "b", + "postfixFontSize": "50%", + "prefix": "a", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2alpha1.json index 1cb5a102169..42c2a374b1c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2alpha1.json @@ -66,6 +66,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "label": "", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -115,6 +175,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -164,6 +284,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -213,6 +393,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -262,6 +502,64 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "µs", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -311,6 +609,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -360,6 +718,64 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -409,6 +825,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -458,6 +934,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -507,6 +1043,64 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -556,6 +1150,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -605,6 +1259,64 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -654,6 +1366,65 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2beta1.json index 6def9ba580b..c1013a5bdcd 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.auto_decimals.v42.v2beta1.json @@ -70,6 +70,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "label": "", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -121,6 +181,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -172,6 +292,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -223,6 +403,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -274,6 +514,64 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "µs", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -325,6 +623,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -376,6 +734,64 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -427,6 +843,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -478,6 +954,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -529,6 +1065,64 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -580,6 +1174,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -631,6 +1285,64 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -682,6 +1394,65 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2alpha1.json index e394514efd5..92b60103171 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2alpha1.json @@ -132,7 +132,68 @@ "kind": "stat", "spec": { "pluginVersion": "6.2.0-pre", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "#73BF69", + "#d44a3a" + ], + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "p99", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -176,7 +237,68 @@ "kind": "stat", "spec": { "pluginVersion": "6.2.0-pre", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "#73BF69", + "#d44a3a" + ], + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "p95", + "prefixFontSize": "80%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -220,7 +342,68 @@ "kind": "stat", "spec": { "pluginVersion": "6.2.0-pre", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "#73BF69", + "#d44a3a" + ], + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "p90", + "prefixFontSize": "80%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -302,6 +485,71 @@ "spec": { "pluginVersion": "6.2.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "blue", + "B-series": "dark-purple", + "C-series": "purple", + "Q-series": "dark-blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 3, + "fillGradient": 0, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -347,6 +595,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -392,6 +700,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "red" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 0, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -476,6 +844,71 @@ "spec": { "pluginVersion": "6.2.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "blue", + "B-series": "dark-purple", + "C-series": "purple", + "Q-series": "dark-blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 3, + "fillGradient": 0, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -521,6 +954,64 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -566,7 +1057,68 @@ "kind": "stat", "spec": { "pluginVersion": "6.2.0-pre", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "#73BF69", + "#d44a3a" + ], + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "p90", + "prefixFontSize": "80%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -610,6 +1162,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "green" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -655,6 +1267,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -700,6 +1372,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "green" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -745,6 +1477,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "green" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -790,6 +1582,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -835,6 +1687,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "purple" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -880,6 +1792,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "orange" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -964,6 +1936,71 @@ "spec": { "pluginVersion": "6.2.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "blue", + "B-series": "dark-purple", + "C-series": "purple", + "Q-series": "dark-blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 3, + "fillGradient": 0, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -1009,6 +2046,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "green" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -1054,6 +2151,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -1099,6 +2256,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "purple" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -1144,6 +2361,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "orange" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2beta1.json index eb82d064496..b1b07fa0f56 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.lazy_loading.v42.v2beta1.json @@ -140,7 +140,68 @@ "group": "stat", "version": "6.2.0-pre", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "#73BF69", + "#d44a3a" + ], + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "p99", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -187,7 +248,68 @@ "group": "stat", "version": "6.2.0-pre", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "#73BF69", + "#d44a3a" + ], + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "p95", + "prefixFontSize": "80%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -234,7 +356,68 @@ "group": "stat", "version": "6.2.0-pre", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "#73BF69", + "#d44a3a" + ], + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "p90", + "prefixFontSize": "80%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -325,6 +508,71 @@ "version": "6.2.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "blue", + "B-series": "dark-purple", + "C-series": "purple", + "Q-series": "dark-blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 3, + "fillGradient": 0, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -373,6 +621,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -421,6 +729,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "red" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 0, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -514,6 +882,71 @@ "version": "6.2.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "blue", + "B-series": "dark-purple", + "C-series": "purple", + "Q-series": "dark-blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 3, + "fillGradient": 0, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -562,6 +995,64 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -610,7 +1101,68 @@ "group": "stat", "version": "6.2.0-pre", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "#73BF69", + "#d44a3a" + ], + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "p90", + "prefixFontSize": "80%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -657,6 +1209,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "green" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -705,6 +1317,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -753,6 +1425,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "green" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -801,6 +1533,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "green" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -849,6 +1641,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -897,6 +1749,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "purple" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -945,6 +1857,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "orange" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -1038,6 +2010,71 @@ "version": "6.2.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "blue", + "B-series": "dark-purple", + "C-series": "purple", + "Q-series": "dark-blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 3, + "fillGradient": 0, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -1086,6 +2123,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "green" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -1134,6 +2231,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -1182,6 +2339,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "purple" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -1230,6 +2447,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "orange" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2alpha1.json index d759c27c39b..0572d6b73c0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2alpha1.json @@ -67,7 +67,68 @@ "kind": "stat", "spec": { "pluginVersion": "6.2.0-pre", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -258,7 +319,65 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -354,7 +473,68 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "#5794F2", + "#d44a3a" + ], + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "100%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": true, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -515,7 +695,68 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "#5794F2", + "#d44a3a" + ], + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "100%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": true, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -676,7 +917,65 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -819,7 +1118,68 @@ "kind": "stat", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "#5794F2", + "#d44a3a" + ], + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "100%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": true, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2beta1.json index 1143716a2b3..deca04a6634 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-common/v0alpha1.panels_without_title.v42.v2beta1.json @@ -71,7 +71,68 @@ "group": "stat", "version": "6.2.0-pre", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -268,7 +329,65 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -368,7 +487,68 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "#5794F2", + "#d44a3a" + ], + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "100%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": true, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -534,7 +714,68 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "#5794F2", + "#d44a3a" + ], + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "100%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": true, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -700,7 +941,65 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -847,7 +1146,68 @@ "group": "stat", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "singlestat", + "originalOptions": { + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "#5794F2", + "#d44a3a" + ], + "format": "ms", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "100%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": true, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "120%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2alpha1.json index 12c84560a90..ef393dae560 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2alpha1.json @@ -66,7 +66,68 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "green" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 0, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -114,7 +175,68 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "rgb(87, 186, 242)" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 4, + "fillGradient": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -162,7 +284,68 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "rgb(48, 139, 237)" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -210,7 +393,68 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "red" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decgbytes", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2beta1.json index 9ae939f5d4f..e6def19e038 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-gradient-area-fills.v42.v2beta1.json @@ -70,7 +70,68 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "green" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "fillGradient": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 0, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -120,7 +181,68 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "rgb(87, 186, 242)" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 4, + "fillGradient": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -170,7 +292,68 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "rgb(48, 139, 237)" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -220,7 +403,68 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "red" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decgbytes", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2alpha1.json index e0203a652e2..6aab259eb23 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2alpha1.json @@ -63,6 +63,65 @@ "spec": { "pluginVersion": "7.5.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { @@ -700,6 +759,65 @@ "spec": { "pluginVersion": "7.5.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "celsius", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2beta1.json index fe835b1b44e..2ba9ec92cb6 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-shared-tooltips.v42.v2beta1.json @@ -67,6 +67,65 @@ "version": "7.5.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { @@ -717,6 +776,65 @@ "version": "7.5.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "celsius", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2alpha1.json index e09a03ec44a..894c33abef1 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2alpha1.json @@ -61,7 +61,78 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [ + { + "colorMode": "gray", + "fill": true, + "fillColor": "rgba(255, 255, 255, 0.03)", + "from": "08:30", + "fromDayOfWeek": 1, + "line": false, + "lineColor": "rgba(255, 255, 255, 0.2)", + "op": "time", + "to": "16:45", + "toDayOfWeek": 5 + } + ], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -104,7 +175,140 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "#d683ce" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 0.5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [ + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(255, 0, 0, 0.22)", + "from": "", + "fromDayOfWeek": 1, + "line": true, + "lineColor": "rgba(255, 0, 0, 0.32)", + "op": "time", + "to": "", + "toDayOfWeek": 1 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(255, 127, 0, 0.22)", + "fromDayOfWeek": 2, + "line": true, + "lineColor": "rgba(255, 127, 0, 0.32)", + "op": "time", + "toDayOfWeek": 2 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(255, 255, 0, 0.22)", + "fromDayOfWeek": 3, + "line": true, + "lineColor": "rgba(255, 255, 0, 0.22)", + "op": "time", + "toDayOfWeek": 3 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(0, 255, 0, 0.22)", + "fromDayOfWeek": 4, + "line": true, + "lineColor": "rgba(0, 255, 0, 0.32)", + "op": "time", + "toDayOfWeek": 4 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(0, 0, 255, 0.22)", + "fromDayOfWeek": 5, + "line": true, + "lineColor": "rgba(0, 0, 255, 0.32)", + "op": "time", + "toDayOfWeek": 5 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(75, 0, 130, 0.22)", + "fromDayOfWeek": 6, + "line": true, + "lineColor": "rgba(75, 0, 130, 0.32)", + "op": "time", + "toDayOfWeek": 6 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(148, 0, 211, 0.22)", + "fromDayOfWeek": 7, + "line": true, + "lineColor": "rgba(148, 0, 211, 0.32)", + "op": "time", + "toDayOfWeek": 7 + } + ], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -150,7 +354,78 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [ + { + "colorMode": "red", + "fill": true, + "fillColor": "rgba(255, 255, 255, 0.03)", + "from": "20:00", + "fromDayOfWeek": 7, + "line": false, + "lineColor": "rgba(255, 255, 255, 0.2)", + "op": "time", + "to": "23:00", + "toDayOfWeek": 7 + } + ], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -196,7 +471,73 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [ + { + "colorMode": "red", + "fill": false, + "from": "05:00", + "line": true, + "op": "time" + } + ], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -238,7 +579,76 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [ + { + "colorMode": "gray", + "fill": true, + "fillColor": "rgba(234, 112, 112, 0.12)", + "from": "22:00", + "line": false, + "lineColor": "rgba(237, 46, 24, 0.60)", + "op": "time", + "to": "00:30" + } + ], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2beta1.json index f6eaa477161..69aceb23017 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph-time-regions.v42.v2beta1.json @@ -66,7 +66,78 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [ + { + "colorMode": "gray", + "fill": true, + "fillColor": "rgba(255, 255, 255, 0.03)", + "from": "08:30", + "fromDayOfWeek": 1, + "line": false, + "lineColor": "rgba(255, 255, 255, 0.2)", + "op": "time", + "to": "16:45", + "toDayOfWeek": 5 + } + ], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -112,7 +183,140 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": { + "A-series": "#d683ce" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 0.5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [ + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(255, 0, 0, 0.22)", + "from": "", + "fromDayOfWeek": 1, + "line": true, + "lineColor": "rgba(255, 0, 0, 0.32)", + "op": "time", + "to": "", + "toDayOfWeek": 1 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(255, 127, 0, 0.22)", + "fromDayOfWeek": 2, + "line": true, + "lineColor": "rgba(255, 127, 0, 0.32)", + "op": "time", + "toDayOfWeek": 2 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(255, 255, 0, 0.22)", + "fromDayOfWeek": 3, + "line": true, + "lineColor": "rgba(255, 255, 0, 0.22)", + "op": "time", + "toDayOfWeek": 3 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(0, 255, 0, 0.22)", + "fromDayOfWeek": 4, + "line": true, + "lineColor": "rgba(0, 255, 0, 0.32)", + "op": "time", + "toDayOfWeek": 4 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(0, 0, 255, 0.22)", + "fromDayOfWeek": 5, + "line": true, + "lineColor": "rgba(0, 0, 255, 0.32)", + "op": "time", + "toDayOfWeek": 5 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(75, 0, 130, 0.22)", + "fromDayOfWeek": 6, + "line": true, + "lineColor": "rgba(75, 0, 130, 0.32)", + "op": "time", + "toDayOfWeek": 6 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(148, 0, 211, 0.22)", + "fromDayOfWeek": 7, + "line": true, + "lineColor": "rgba(148, 0, 211, 0.32)", + "op": "time", + "toDayOfWeek": 7 + } + ], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -161,7 +365,78 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [ + { + "colorMode": "red", + "fill": true, + "fillColor": "rgba(255, 255, 255, 0.03)", + "from": "20:00", + "fromDayOfWeek": 7, + "line": false, + "lineColor": "rgba(255, 255, 255, 0.2)", + "op": "time", + "to": "23:00", + "toDayOfWeek": 7 + } + ], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -210,7 +485,73 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [ + { + "colorMode": "red", + "fill": false, + "from": "05:00", + "line": true, + "op": "time" + } + ], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -255,7 +596,76 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [ + { + "colorMode": "gray", + "fill": true, + "fillColor": "rgba(234, 112, 112, 0.12)", + "from": "22:00", + "line": false, + "lineColor": "rgba(237, 46, 24, 0.60)", + "op": "time", + "to": "00:30" + } + ], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json index e67e659b5e8..f93a54ad34d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json @@ -62,7 +62,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -106,7 +166,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -183,7 +303,72 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "B-series", + "zindex": -3 + } + ], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -404,7 +589,66 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "decimals": 3, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -583,7 +827,66 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "decimals": 3, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -672,7 +975,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "decimals": 3, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -881,7 +1244,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "decimals": 3, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -925,7 +1348,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -969,7 +1452,66 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "decimals": 3, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1030,7 +1572,72 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "C-series", + "steppedLine": true + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1118,7 +1725,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1164,7 +1831,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1223,7 +1950,74 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "B-series", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "label": "Percent", + "logBase": 1, + "show": true + }, + { + "format": "short", + "label": "Pressure", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1355,7 +2149,67 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1431,7 +2285,72 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "B-series", + "zindex": -3 + } + ], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json index 7a94d1842a5..d611243ea1d 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json @@ -67,7 +67,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -114,7 +174,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -198,7 +318,72 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "B-series", + "zindex": -3 + } + ], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -434,7 +619,66 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "decimals": 3, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -634,7 +878,66 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "decimals": 3, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -732,7 +1035,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "decimals": 3, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -966,7 +1329,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "decimals": 3, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1013,7 +1436,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1060,7 +1543,66 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "decimals": 3, + "fill": 1, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1126,7 +1668,72 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "C-series", + "steppedLine": true + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1219,7 +1826,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1268,7 +1935,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1332,7 +2059,74 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "B-series", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "label": "Percent", + "logBase": 1, + "show": true + }, + { + "format": "short", + "label": "Pressure", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1471,7 +2265,67 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1554,7 +2408,72 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "B-series", + "zindex": -3 + } + ], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2alpha1.json index 25ef1324bc4..f060ff213d2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2alpha1.json @@ -66,6 +66,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "label": "", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -115,6 +175,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -164,6 +284,64 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -213,6 +391,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -262,6 +500,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -311,6 +609,64 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -360,6 +716,66 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -409,6 +825,64 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -458,6 +932,65 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2beta1.json index 4534fe38da4..8202a6a7dd7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_y_axis.v42.v2beta1.json @@ -70,6 +70,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "label": "", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -121,6 +181,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -172,6 +292,64 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -223,6 +401,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -274,6 +512,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -325,6 +623,64 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -376,6 +732,66 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "max": "10000", + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -427,6 +843,64 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ms", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -478,6 +952,65 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2alpha1.json index 2277a05c6bb..dc0aab46631 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2alpha1.json @@ -109,6 +109,65 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2beta1.json index 2f61f5977d3..50419f5a194 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-library/v0alpha1.panel-library.v42.v2beta1.json @@ -113,6 +113,65 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2alpha1.json index 2bcedabe196..c706000d557 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2alpha1.json @@ -80,7 +80,85 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colorMode": "cell", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "ColorCell", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "ColorValue", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "Bps" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "timeseries_to_columns" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -142,7 +220,86 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "pageSize": 10, + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colorMode": "cell", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "ColorCell", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "ColorValue", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "Bps" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "timeseries_to_rows" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -204,7 +361,99 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [ + { + "text": "Avg", + "value": "avg" + }, + { + "text": "Max", + "value": "max" + }, + { + "text": "Current", + "value": "current" + } + ], + "fontSize": "100%", + "pageSize": 10, + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colorMode": "cell", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "ColorCell", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "ColorValue", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "Bps" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "timeseries_aggregations" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -251,7 +500,65 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colorMode": "row", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "/Color/", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "timeseries_to_columns" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -313,7 +620,92 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "pageSize": 20, + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colorMode": "cell", + "colors": [ + "rgba(245, 54, 54, 0.5)", + "rgba(237, 129, 40, 0.5)", + "rgba(50, 172, 45, 0.5)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "link": true, + "linkTargetBlank": true, + "linkTooltip": "", + "linkUrl": "http://www.grafana.com", + "mappingType": 1, + "pattern": "ColorCell", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.5)", + "rgba(237, 129, 40, 0.5)", + "rgba(50, 172, 45, 0.5)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "link": true, + "linkUrl": "http://www.grafana.com", + "mappingType": 1, + "pattern": "ColorValue", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "Bps" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "timeseries_to_columns" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2beta1.json index 2d84b370019..3ddb128c20e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-table/v0alpha1.table_tests.v42.v2beta1.json @@ -87,7 +87,85 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colorMode": "cell", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "ColorCell", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "ColorValue", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "Bps" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "timeseries_to_columns" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -154,7 +232,86 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "pageSize": 10, + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colorMode": "cell", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "ColorCell", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "ColorValue", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "Bps" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "timeseries_to_rows" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -221,7 +378,99 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [ + { + "text": "Avg", + "value": "avg" + }, + { + "text": "Max", + "value": "max" + }, + { + "text": "Current", + "value": "current" + } + ], + "fontSize": "100%", + "pageSize": 10, + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colorMode": "cell", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "ColorCell", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "ColorValue", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "Bps" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "timeseries_aggregations" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -271,7 +520,65 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colorMode": "row", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "/Color/", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "timeseries_to_columns" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -338,7 +645,92 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "pageSize": 20, + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "auto", + "colorMode": "cell", + "colors": [ + "rgba(245, 54, 54, 0.5)", + "rgba(237, 129, 40, 0.5)", + "rgba(50, 172, 45, 0.5)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "link": true, + "linkTargetBlank": true, + "linkTooltip": "", + "linkUrl": "http://www.grafana.com", + "mappingType": 1, + "pattern": "ColorCell", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "currencyUSD" + }, + { + "alias": "", + "align": "auto", + "colorMode": "value", + "colors": [ + "rgba(245, 54, 54, 0.5)", + "rgba(237, 129, 40, 0.5)", + "rgba(50, 172, 45, 0.5)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "link": true, + "linkUrl": "http://www.grafana.com", + "mappingType": 1, + "pattern": "ColorValue", + "thresholds": [ + "5", + "10" + ], + "type": "number", + "unit": "Bps" + }, + { + "alias": "", + "align": "auto", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "timeseries_to_columns" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2alpha1.json index c9a9c26558e..b15dedc73a8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2alpha1.json @@ -176,6 +176,88 @@ "spec": { "pluginVersion": "7.5.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [ + { + "$$hashKey": "object:14", + "colorMode": "critical", + "fill": false, + "line": true, + "op": "gt", + "value": 80, + "yaxis": "left" + }, + { + "$$hashKey": "object:44", + "colorMode": "warning", + "fill": false, + "line": true, + "op": "gt", + "value": 50, + "yaxis": "left" + } + ], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:26", + "format": "short", + "logBase": 1, + "max": "100", + "min": "0", + "show": true + }, + { + "$$hashKey": "object:27", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { @@ -339,6 +421,88 @@ "spec": { "pluginVersion": "7.5.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [ + { + "$$hashKey": "object:109", + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 20, + "yaxis": "left" + }, + { + "$$hashKey": "object:115", + "colorMode": "warning", + "fill": true, + "line": true, + "op": "lt", + "value": 60, + "yaxis": "left" + } + ], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:26", + "format": "short", + "logBase": 1, + "max": "100", + "min": "0", + "show": true + }, + { + "$$hashKey": "object:27", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { @@ -826,6 +990,88 @@ "spec": { "pluginVersion": "7.5.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [ + { + "$$hashKey": "object:14", + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 80, + "yaxis": "left" + }, + { + "$$hashKey": "object:44", + "colorMode": "warning", + "fill": true, + "line": true, + "op": "gt", + "value": 50, + "yaxis": "left" + } + ], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:26", + "format": "short", + "logBase": 1, + "max": "100", + "min": "0", + "show": true + }, + { + "$$hashKey": "object:27", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { @@ -876,6 +1122,88 @@ "spec": { "pluginVersion": "7.5.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [ + { + "$$hashKey": "object:109", + "colorMode": "critical", + "fill": true, + "line": false, + "op": "gt", + "value": 80, + "yaxis": "left" + }, + { + "$$hashKey": "object:115", + "colorMode": "warning", + "fill": true, + "line": false, + "op": "gt", + "value": 60, + "yaxis": "left" + } + ], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:26", + "format": "short", + "logBase": 1, + "max": "100", + "min": "0", + "show": true + }, + { + "$$hashKey": "object:27", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { @@ -928,6 +1256,97 @@ "spec": { "pluginVersion": "7.5.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 0, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 1, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [ + { + "$$hashKey": "object:14", + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 80, + "yaxis": "left" + }, + { + "$$hashKey": "object:44", + "colorMode": "warning", + "fill": true, + "line": true, + "op": "lt", + "value": 40, + "yaxis": "left" + }, + { + "$$hashKey": "object:40", + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 20, + "yaxis": "left" + } + ], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:26", + "format": "short", + "logBase": 1, + "max": "100", + "min": "0", + "show": true + }, + { + "$$hashKey": "object:27", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { @@ -980,6 +1399,92 @@ "spec": { "pluginVersion": "7.5.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 0, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 1, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [ + { + "$$hashKey": "object:14", + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(50, 161, 230, 0.13)", + "line": true, + "lineColor": "#B877D9", + "op": "gt", + "value": 80, + "yaxis": "left" + }, + { + "$$hashKey": "object:44", + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(184, 119, 217, 0.23)", + "line": true, + "lineColor": "rgba(93, 196, 31, 0.6)", + "op": "lt", + "value": 40, + "yaxis": "left" + } + ], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:26", + "format": "short", + "logBase": 1, + "max": "100", + "min": "0", + "show": true + }, + { + "$$hashKey": "object:27", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2beta1.json index 55ed3cf155c..0e9b32d82cf 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-timeseries/v0alpha1.timeseries-thresholds.v42.v2beta1.json @@ -182,6 +182,88 @@ "version": "7.5.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [ + { + "$$hashKey": "object:14", + "colorMode": "critical", + "fill": false, + "line": true, + "op": "gt", + "value": 80, + "yaxis": "left" + }, + { + "$$hashKey": "object:44", + "colorMode": "warning", + "fill": false, + "line": true, + "op": "gt", + "value": 50, + "yaxis": "left" + } + ], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:26", + "format": "short", + "logBase": 1, + "max": "100", + "min": "0", + "show": true + }, + { + "$$hashKey": "object:27", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { @@ -349,6 +431,88 @@ "version": "7.5.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [ + { + "$$hashKey": "object:109", + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 20, + "yaxis": "left" + }, + { + "$$hashKey": "object:115", + "colorMode": "warning", + "fill": true, + "line": true, + "op": "lt", + "value": 60, + "yaxis": "left" + } + ], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:26", + "format": "short", + "logBase": 1, + "max": "100", + "min": "0", + "show": true + }, + { + "$$hashKey": "object:27", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { @@ -846,6 +1010,88 @@ "version": "7.5.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [ + { + "$$hashKey": "object:14", + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 80, + "yaxis": "left" + }, + { + "$$hashKey": "object:44", + "colorMode": "warning", + "fill": true, + "line": true, + "op": "gt", + "value": 50, + "yaxis": "left" + } + ], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:26", + "format": "short", + "logBase": 1, + "max": "100", + "min": "0", + "show": true + }, + { + "$$hashKey": "object:27", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { @@ -898,6 +1144,88 @@ "version": "7.5.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [ + { + "$$hashKey": "object:109", + "colorMode": "critical", + "fill": true, + "line": false, + "op": "gt", + "value": 80, + "yaxis": "left" + }, + { + "$$hashKey": "object:115", + "colorMode": "warning", + "fill": true, + "line": false, + "op": "gt", + "value": 60, + "yaxis": "left" + } + ], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:26", + "format": "short", + "logBase": 1, + "max": "100", + "min": "0", + "show": true + }, + { + "$$hashKey": "object:27", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { @@ -952,6 +1280,97 @@ "version": "7.5.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 0, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 1, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [ + { + "$$hashKey": "object:14", + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 80, + "yaxis": "left" + }, + { + "$$hashKey": "object:44", + "colorMode": "warning", + "fill": true, + "line": true, + "op": "lt", + "value": 40, + "yaxis": "left" + }, + { + "$$hashKey": "object:40", + "colorMode": "critical", + "fill": true, + "line": true, + "op": "lt", + "value": 20, + "yaxis": "left" + } + ], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:26", + "format": "short", + "logBase": 1, + "max": "100", + "min": "0", + "show": true + }, + { + "$$hashKey": "object:27", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { @@ -1006,6 +1425,92 @@ "version": "7.5.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 0, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 1, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [ + { + "$$hashKey": "object:14", + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(50, 161, 230, 0.13)", + "line": true, + "lineColor": "#B877D9", + "op": "gt", + "value": 80, + "yaxis": "left" + }, + { + "$$hashKey": "object:44", + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(184, 119, 217, 0.23)", + "line": true, + "lineColor": "rgba(93, 196, 31, 0.6)", + "op": "lt", + "value": 40, + "yaxis": "left" + } + ], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:26", + "format": "short", + "logBase": 1, + "max": "100", + "min": "0", + "show": true + }, + { + "$$hashKey": "object:27", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "alertThreshold": true }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2alpha1.json index c8314d47e25..addd25f950f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2alpha1.json @@ -86,7 +86,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -132,7 +189,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -178,7 +292,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -224,7 +395,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -270,7 +498,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -316,7 +601,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -362,7 +704,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -408,7 +807,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -454,7 +910,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -500,7 +1013,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -546,7 +1116,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -592,7 +1219,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -638,7 +1322,64 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2beta1.json index 90ce6da65a3..427a41ec1d7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.slow_queries_and_annotations.v42.v2beta1.json @@ -93,7 +93,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -142,7 +199,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -191,7 +305,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -240,7 +411,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -289,7 +517,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -338,7 +623,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -387,7 +729,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -436,7 +835,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -485,7 +941,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -534,7 +1047,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -583,7 +1153,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -632,7 +1259,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -681,7 +1365,64 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2alpha1.json index c3413246704..f95a6880b61 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2alpha1.json @@ -108,6 +108,67 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "decimals": 3, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -171,6 +232,73 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 0, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "C-series", + "steppedLine": true + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -217,6 +345,68 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -265,6 +455,68 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -326,6 +578,75 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "B-series", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "label": "Perecent", + "logBase": 1, + "show": true + }, + { + "format": "short", + "label": "Pressure", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -404,6 +725,73 @@ "spec": { "pluginVersion": "", "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "B-series", + "zindex": -3 + } + ], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2beta1.json index b2146b3b82a..46da655452b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/scenarios/v0alpha1.time_zone_support.v42.v2beta1.json @@ -119,6 +119,67 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "decimals": 3, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -187,6 +248,73 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 0, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "C-series", + "steppedLine": true + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -236,6 +364,68 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -287,6 +477,68 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -353,6 +605,75 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "B-series", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "label": "Perecent", + "logBase": 1, + "show": true + }, + { + "format": "short", + "label": "Pressure", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { @@ -438,6 +759,73 @@ "version": "", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "editable": true, + "error": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "B-series", + "zindex": -3 + } + ], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, "dataLinks": [] }, "fieldConfig": { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.angular-migrations.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.angular-migrations.v0alpha1.json new file mode 100644 index 00000000000..d1253a0a81d --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.angular-migrations.v0alpha1.json @@ -0,0 +1,1310 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "angular-migrations-test" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate graph panel (TRUE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateGraphPanel=true" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate graph panel (FALSE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateGraphPanel=false" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate table (old) panel (TRUE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateTablePanel=true" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate table (old) panel (FALSE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateTablePanel=false" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate piechart panel (TRUE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigratePiechartPanel=true" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate piechart panel (FALSE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigratePiechartPanel=false" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate worldmap panel (TRUE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateWorldmapPanel=true" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate worldmap panel (FALSE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateWorldmapPanel=false" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate stat panel (TRUE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateStatPanel=true" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate stat panel (FALSE)", + "tooltip": "", + "type": "link", + "url": " /d/cdd412c4/?__feature.autoMigrateStatPanel=false" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate (TRUE)", + "tooltip": "", + "type": "link", + "url": "/d/cdd412c4/?__feature.autoMigrateOldPanels=true" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Auto migrate (FALSE)", + "tooltip": "", + "type": "link", + "url": "/d/cdd412c4/?__feature.autoMigrateOldPanels=false" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Disable angular (TRUE)", + "tooltip": "", + "type": "link", + "url": "/d/cdd412c4/?__feature.disableAngular=true" + }, + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": true, + "title": "Disable angular (FALSE)", + "tooltip": "", + "type": "link", + "url": "/d/cdd412c4/?__feature.disableAngular=false" + } + ], + "liveNow": false, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 16, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "percentage": false, + "pluginVersion": "11.0.0-pre", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 3 + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Flot graph", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 11, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 6, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", + "mode": "markdown" + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "aliasColors": {}, + "autoMigrateFrom": "graph", + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 16, + "x": 0, + "y": 11 + }, + "hiddenSeries": false, + "id": 28, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "percentage": false, + "pluginVersion": "11.0.0-pre", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ],\n \"custom\": {\n \"customStat\": 10\n }\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 3600000\n }\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {\n \"pod\": \"A-pod\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1727107111901,\n 1727110711901,\n 1727114311901,\n 1727117911901,\n 1727121511901,\n 1727125111901\n ],\n [\n 1.907286825122581,\n 2.260951647569786,\n 1.887442338051216,\n 2.1526144400893514,\n 1.7287721375237766,\n 1.7262902137793208\n ]\n ]\n }\n },\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ],\n \"custom\": {\n \"customStat\": 10\n }\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 3600000\n }\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {\n \"pod\": \"A-pod1\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1727107111901,\n 1727110711901,\n 1727114311901,\n 1727117911901,\n 1727121511901,\n 1727125111901\n ],\n [\n 1.907286825122581,\n 1.589539045095202,\n 1.5914283506847613,\n 1.8976990616650726,\n 1.758223085999124,\n 2.2294649594813816\n ]\n ]\n }\n },\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ],\n \"custom\": {\n \"customStat\": 10\n }\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 3600000\n }\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {\n \"pod\": \"A-pod2\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1727107111901,\n 1727110711901,\n 1727114311901,\n 1727117911901,\n 1727121511901,\n 1727125111901\n ],\n [\n 1.907286825122581,\n 2.0914263380328766,\n 1.8164545521094575,\n 1.621111084665713,\n 1.3902653996444705,\n 1.482803315949775\n ]\n ]\n }\n }\n]", + "refId": "A", + "scenarioId": "raw_frame" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Flot graph - x axis series mode", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "type": "barchart", + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:88", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:89", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 11, + "w": 8, + "x": 16, + "y": 11 + }, + "id": 29, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Bar chart panel\n", + "mode": "markdown" + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "aliasColors": {}, + "autoMigrateFrom": "graph", + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": { + "default": false, + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 16, + "x": 0, + "y": 22 + }, + "hiddenSeries": false, + "id": 32, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "percentage": false, + "pluginVersion": "11.3.0-pre", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 28, + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Flot graph - x axis series mode (with legend calcs)", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "type": "bargauge", + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:88", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:89", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "autoMigrateFrom": "graph", + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 16, + "x": 0, + "y": 22 + }, + "hiddenSeries": false, + "id": 30, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "percentage": false, + "pluginVersion": "11.0.0-pre", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 3 + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Flot graph - x axis histogram mode", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "type": "histogram", + "xaxis": { + "mode": "histogram", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:193", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:194", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 11, + "w": 8, + "x": 16, + "y": 22 + }, + "id": 33, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Bar gauge panel\n", + "mode": "markdown" + }, + "pluginVersion": "11.3.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 11, + "w": 8, + "x": 16, + "y": 22 + }, + "id": 31, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Histogram panel\n", + "mode": "markdown" + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "autoMigrateFrom": "table-old", + "columns": [], + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fontSize": "100%", + "gridPos": { + "h": 10, + "w": 16, + "x": 0, + "y": 33 + }, + "id": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "showRowNums": false + }, + "pluginVersion": "9.5.0-pre", + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "right", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "random_walk_table" + } + ], + "title": "Table (old)", + "transform": "table", + "transformations": [ + { + "id": "merge", + "options": { + "reducers": [] + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 16, + "y": 33 + }, + "id": 7, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Table (old) \u003e\u003e Table\n\nKnown issues:\n* wrapping text\n* style changes", + "mode": "markdown" + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "autoMigrateFrom": "grafana-singlestat-panel", + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": { + "type": "grafana-testdata-datasource" + }, + "format": "areaF2", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 43 + }, + "id": 9, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "postfix": "b", + "postfixFontSize": "50%", + "prefix": "a", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "thresholds": "", + "title": "grafana-singlestat-panel", + "type": "stat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 43 + }, + "id": 23, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "singlestat (old, internal. Migrated if schema \u003c 28)", + "type": "stat" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 43 + }, + "id": 10, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Singlestat \u003e\u003e Stat\n\nKnown issues:\n* limited options", + "mode": "markdown" + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "autoMigrateFrom": "grafana-piechart-panel", + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 10, + "w": 16, + "x": 0, + "y": 51 + }, + "id": 24, + "options": { + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.5.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "random_walk_table" + } + ], + "title": "grafana-piechart-panel", + "transformations": [ + { + "id": "merge", + "options": { + "reducers": [] + } + } + ], + "type": "piechart" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 16, + "y": 51 + }, + "id": 25, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# grafana-piechart-panel \u003e\u003e piechart\n\nKnown issues:\n* TBD", + "mode": "markdown" + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + }, + { + "autoMigrateFrom": "grafana-worldmap-panel", + "circleMaxSize": 30, + "circleMinSize": 2, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "datasource": { + "type": "grafana-testdata-datasource" + }, + "decimals": 0, + "esMetric": "Count", + "gridPos": { + "h": 10, + "w": 16, + "x": 0, + "y": 61 + }, + "hideEmpty": false, + "hideZero": false, + "id": 26, + "initialZoom": 1, + "locationData": "countries", + "mapCenter": "(0°, 0°)", + "mapCenterLatitude": 0, + "mapCenterLongitude": 0, + "maxDataPoints": 1, + "mouseWheelZoom": false, + "options": { + "basemap": { + "name": "Basemap", + "type": "default" + }, + "controls": { + "mouseWheelZoom": false, + "showAttribution": true, + "showDebug": false, + "showMeasure": false, + "showScale": false, + "showZoom": true + }, + "layers": [ + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "dark-green" + }, + "opacity": 0.4, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 5, + "max": 30, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "location": { + "gazetteer": "public/gazetteer/countries.json", + "mode": "lookup" + }, + "name": "Layer 0", + "tooltip": true, + "type": "markers" + } + ], + "tooltip": { + "mode": "details" + }, + "view": { + "allLayers": true, + "id": "zero", + "lat": 0, + "lon": 0, + "zoom": 1 + } + }, + "pluginVersion": "10.4.0-pre", + "showLegend": true, + "stickyLabels": false, + "tableQueryOptions": { + "geohashField": "geohash", + "latitudeField": "latitude", + "longitudeField": "longitude", + "metricField": "metric", + "queryType": "geohash" + }, + "targets": [ + { + "csvFileName": "flight_info_by_state.csv", + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A", + "scenarioId": "csv_file" + } + ], + "thresholds": "0,10", + "title": "grafana-worldmap-panel", + "transformations": [ + { + "id": "merge", + "options": { + "reducers": [] + } + }, + { + "id": "reduce", + "options": { + "reducers": [ + "sum" + ] + } + } + ], + "type": "geomap", + "unitPlural": "", + "unitSingle": "", + "valueName": "total" + }, + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "gridPos": { + "h": 10, + "w": 8, + "x": 16, + "y": 61 + }, + "id": 27, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# grafana-worldmap-panel \u003e\u003e geomap\n\nKnown issues:\n* TBD", + "mode": "markdown" + }, + "pluginVersion": "11.0.0-pre", + "targets": [ + { + "datasource": { + "type": "grafana-testdata-datasource" + }, + "refId": "A" + } + ], + "title": "Status + Notes", + "type": "text" + } + ], + "refresh": "", + "schemaVersion": 42, + "tags": [ + "gdev", + "migrations", + "angular" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Devenv - Panel migrations", + "uid": "cdd412c4", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.angular-migrations.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.angular-migrations.v2alpha1.json new file mode 100644 index 00000000000..d9e72ec700a --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.angular-migrations.v2alpha1.json @@ -0,0 +1,1861 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "angular-migrations-test" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "grafana", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "11.0.0-pre", + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Singlestat \u003e\u003e Stat\n\nKnown issues:\n* limited options", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Table (old)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": { + "scenarioId": "random_walk_table" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "merge", + "spec": { + "id": "merge", + "options": { + "reducers": [] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "9.5.0-pre", + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "right", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + }, + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "showRowNums": false + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-23": { + "kind": "Panel", + "spec": { + "id": 23, + "title": "singlestat (old, internal. Migrated if schema \u003c 28)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": { + "maxDataPoints": 100 + } + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "11.0.0-pre", + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "fieldConfig": { + "defaults": { + "unit": "ms", + "mappings": [ + { + "type": "special", + "options": { + "match": "null", + "result": { + "text": "N/A" + } + } + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + } + }, + "overrides": [] + } + } + } + } + }, + "panel-24": { + "kind": "Panel", + "spec": { + "id": 24, + "title": "grafana-piechart-panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": { + "scenarioId": "random_walk_table" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "merge", + "spec": { + "id": "merge", + "options": { + "reducers": [] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "piechart", + "spec": { + "pluginVersion": "9.5.0-pre", + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-piechart-panel", + "originalOptions": {} + }, + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-25": { + "kind": "Panel", + "spec": { + "id": 25, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "11.0.0-pre", + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# grafana-piechart-panel \u003e\u003e piechart\n\nKnown issues:\n* TBD", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-26": { + "kind": "Panel", + "spec": { + "id": 26, + "title": "grafana-worldmap-panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": { + "csvFileName": "flight_info_by_state.csv", + "scenarioId": "csv_file" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "merge", + "spec": { + "id": "merge", + "options": { + "reducers": [] + } + } + }, + { + "kind": "reduce", + "spec": { + "id": "reduce", + "options": { + "reducers": [ + "sum" + ] + } + } + } + ], + "queryOptions": { + "maxDataPoints": 1 + } + } + }, + "vizConfig": { + "kind": "geomap", + "spec": { + "pluginVersion": "10.4.0-pre", + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-worldmap-panel", + "originalOptions": { + "circleMaxSize": 30, + "circleMinSize": 2, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 0, + "esMetric": "Count", + "hideEmpty": false, + "hideZero": false, + "initialZoom": 1, + "locationData": "countries", + "mapCenter": "(0°, 0°)", + "mapCenterLatitude": 0, + "mapCenterLongitude": 0, + "mouseWheelZoom": false, + "showLegend": true, + "stickyLabels": false, + "tableQueryOptions": { + "geohashField": "geohash", + "latitudeField": "latitude", + "longitudeField": "longitude", + "metricField": "metric", + "queryType": "geohash" + }, + "thresholds": "0,10", + "unitPlural": "", + "unitSingle": "", + "valueName": "total" + } + }, + "basemap": { + "name": "Basemap", + "type": "default" + }, + "controls": { + "mouseWheelZoom": false, + "showAttribution": true, + "showDebug": false, + "showMeasure": false, + "showScale": false, + "showZoom": true + }, + "layers": [ + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "dark-green" + }, + "opacity": 0.4, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 5, + "max": 30, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "location": { + "gazetteer": "public/gazetteer/countries.json", + "mode": "lookup" + }, + "name": "Layer 0", + "tooltip": true, + "type": "markers" + } + ], + "tooltip": { + "mode": "details" + }, + "view": { + "allLayers": true, + "id": "zero", + "lat": 0, + "lon": 0, + "zoom": 1 + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-27": { + "kind": "Panel", + "spec": { + "id": 27, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "11.0.0-pre", + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# grafana-worldmap-panel \u003e\u003e geomap\n\nKnown issues:\n* TBD", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-28": { + "kind": "Panel", + "spec": { + "id": 28, + "title": "Flot graph - x axis series mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": { + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ],\n \"custom\": {\n \"customStat\": 10\n }\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 3600000\n }\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {\n \"pod\": \"A-pod\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1727107111901,\n 1727110711901,\n 1727114311901,\n 1727117911901,\n 1727121511901,\n 1727125111901\n ],\n [\n 1.907286825122581,\n 2.260951647569786,\n 1.887442338051216,\n 2.1526144400893514,\n 1.7287721375237766,\n 1.7262902137793208\n ]\n ]\n }\n },\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ],\n \"custom\": {\n \"customStat\": 10\n }\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 3600000\n }\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {\n \"pod\": \"A-pod1\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1727107111901,\n 1727110711901,\n 1727114311901,\n 1727117911901,\n 1727121511901,\n 1727125111901\n ],\n [\n 1.907286825122581,\n 1.589539045095202,\n 1.5914283506847613,\n 1.8976990616650726,\n 1.758223085999124,\n 2.2294649594813816\n ]\n ]\n }\n },\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ],\n \"custom\": {\n \"customStat\": 10\n }\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 3600000\n }\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {\n \"pod\": \"A-pod2\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1727107111901,\n 1727110711901,\n 1727114311901,\n 1727117911901,\n 1727121511901,\n 1727125111901\n ],\n [\n 1.907286825122581,\n 2.0914263380328766,\n 1.8164545521094575,\n 1.621111084665713,\n 1.3902653996444705,\n 1.482803315949775\n ]\n ]\n }\n }\n]", + "scenarioId": "raw_frame" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "barchart", + "spec": { + "pluginVersion": "11.0.0-pre", + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:88", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:89", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + } + } + } + } + }, + "panel-29": { + "kind": "Panel", + "spec": { + "id": 29, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "11.0.0-pre", + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Bar chart panel\n", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-30": { + "kind": "Panel", + "spec": { + "id": 30, + "title": "Flot graph - x axis histogram mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": { + "scenarioId": "random_walk", + "seriesCount": 3 + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "histogram", + "spec": { + "pluginVersion": "11.0.0-pre", + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "histogram", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:193", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:194", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + } + } + } + } + }, + "panel-31": { + "kind": "Panel", + "spec": { + "id": 31, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "11.0.0-pre", + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Histogram panel\n", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-32": { + "kind": "Panel", + "spec": { + "id": 32, + "title": "Flot graph - x axis series mode (with legend calcs)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "datasource", + "spec": { + "panelId": 28 + } + }, + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "bargauge", + "spec": { + "pluginVersion": "11.3.0-pre", + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:88", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:89", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + } + } + } + } + }, + "panel-33": { + "kind": "Panel", + "spec": { + "id": 33, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "11.3.0-pre", + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Bar gauge panel\n", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Flot graph", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": { + "scenarioId": "random_walk", + "seriesCount": 3 + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "11.0.0-pre", + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "11.0.0-pre", + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "text", + "spec": { + "pluginVersion": "11.0.0-pre", + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Table (old) \u003e\u003e Table\n\nKnown issues:\n* wrapping text\n* style changes", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "grafana-singlestat-panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "grafana-testdata-datasource", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": { + "maxDataPoints": 100 + } + } + }, + "vizConfig": { + "kind": "stat", + "spec": { + "pluginVersion": "", + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-singlestat-panel", + "originalOptions": { + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "format": "areaF2", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "b", + "postfixFontSize": "50%", + "prefix": "a", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 16, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 0, + "width": 8, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 11, + "width": 16, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-28" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 11, + "width": 8, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-29" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 22, + "width": 16, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-32" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 22, + "width": 16, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-30" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 22, + "width": 8, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-33" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 22, + "width": 8, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-31" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 33, + "width": 16, + "height": 10, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 33, + "width": 8, + "height": 10, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 43, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 43, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-23" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 43, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 51, + "width": 16, + "height": 10, + "element": { + "kind": "ElementReference", + "name": "panel-24" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 51, + "width": 8, + "height": 10, + "element": { + "kind": "ElementReference", + "name": "panel-25" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 61, + "width": 16, + "height": 10, + "element": { + "kind": "ElementReference", + "name": "panel-26" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 61, + "width": 8, + "height": 10, + "element": { + "kind": "ElementReference", + "name": "panel-27" + } + } + } + ] + } + }, + "links": [ + { + "title": "Auto migrate graph panel (TRUE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateGraphPanel=true", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate graph panel (FALSE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateGraphPanel=false", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate table (old) panel (TRUE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateTablePanel=true", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate table (old) panel (FALSE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateTablePanel=false", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate piechart panel (TRUE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigratePiechartPanel=true", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate piechart panel (FALSE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigratePiechartPanel=false", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate worldmap panel (TRUE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateWorldmapPanel=true", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate worldmap panel (FALSE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateWorldmapPanel=false", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate stat panel (TRUE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateStatPanel=true", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate stat panel (FALSE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateStatPanel=false", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate (TRUE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": "/d/cdd412c4/?__feature.autoMigrateOldPanels=true", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate (FALSE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": "/d/cdd412c4/?__feature.autoMigrateOldPanels=false", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Disable angular (TRUE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": "/d/cdd412c4/?__feature.disableAngular=true", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Disable angular (FALSE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": "/d/cdd412c4/?__feature.disableAngular=false", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + } + ], + "liveNow": false, + "preload": false, + "tags": [ + "gdev", + "migrations", + "angular" + ], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Devenv - Panel migrations", + "variables": [] + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.angular-migrations.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.angular-migrations.v2beta1.json new file mode 100644 index 00000000000..63447487dca --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.angular-migrations.v2beta1.json @@ -0,0 +1,1912 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "angular-migrations-test" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-10": { + "kind": "Panel", + "spec": { + "id": 10, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "11.0.0-pre", + "spec": { + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Singlestat \u003e\u003e Stat\n\nKnown issues:\n* limited options", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Table (old)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": { + "scenarioId": "random_walk_table" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "merge", + "spec": { + "id": "merge", + "options": { + "reducers": [] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "9.5.0-pre", + "spec": { + "options": { + "__angularMigration": { + "autoMigrateFrom": "table-old", + "originalOptions": { + "columns": [], + "fontSize": "100%", + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "styles": [ + { + "alias": "Time", + "align": "auto", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "", + "align": "right", + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "pattern": "/.*/", + "thresholds": [], + "type": "number", + "unit": "short" + } + ], + "transform": "table" + } + }, + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "showRowNums": false + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-23": { + "kind": "Panel", + "spec": { + "id": 23, + "title": "singlestat (old, internal. Migrated if schema \u003c 28)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": { + "maxDataPoints": 100 + } + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "11.0.0-pre", + "spec": { + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "fieldConfig": { + "defaults": { + "unit": "ms", + "mappings": [ + { + "type": "special", + "options": { + "match": "null", + "result": { + "text": "N/A" + } + } + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + } + }, + "overrides": [] + } + } + } + } + }, + "panel-24": { + "kind": "Panel", + "spec": { + "id": 24, + "title": "grafana-piechart-panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": { + "scenarioId": "random_walk_table" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "merge", + "spec": { + "id": "merge", + "options": { + "reducers": [] + } + } + } + ], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "piechart", + "version": "9.5.0-pre", + "spec": { + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-piechart-panel", + "originalOptions": {} + }, + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-25": { + "kind": "Panel", + "spec": { + "id": 25, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "11.0.0-pre", + "spec": { + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# grafana-piechart-panel \u003e\u003e piechart\n\nKnown issues:\n* TBD", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-26": { + "kind": "Panel", + "spec": { + "id": 26, + "title": "grafana-worldmap-panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": { + "csvFileName": "flight_info_by_state.csv", + "scenarioId": "csv_file" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [ + { + "kind": "merge", + "spec": { + "id": "merge", + "options": { + "reducers": [] + } + } + }, + { + "kind": "reduce", + "spec": { + "id": "reduce", + "options": { + "reducers": [ + "sum" + ] + } + } + } + ], + "queryOptions": { + "maxDataPoints": 1 + } + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "geomap", + "version": "10.4.0-pre", + "spec": { + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-worldmap-panel", + "originalOptions": { + "circleMaxSize": 30, + "circleMinSize": 2, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 0, + "esMetric": "Count", + "hideEmpty": false, + "hideZero": false, + "initialZoom": 1, + "locationData": "countries", + "mapCenter": "(0°, 0°)", + "mapCenterLatitude": 0, + "mapCenterLongitude": 0, + "mouseWheelZoom": false, + "showLegend": true, + "stickyLabels": false, + "tableQueryOptions": { + "geohashField": "geohash", + "latitudeField": "latitude", + "longitudeField": "longitude", + "metricField": "metric", + "queryType": "geohash" + }, + "thresholds": "0,10", + "unitPlural": "", + "unitSingle": "", + "valueName": "total" + } + }, + "basemap": { + "name": "Basemap", + "type": "default" + }, + "controls": { + "mouseWheelZoom": false, + "showAttribution": true, + "showDebug": false, + "showMeasure": false, + "showScale": false, + "showZoom": true + }, + "layers": [ + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "dark-green" + }, + "opacity": 0.4, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod" + }, + "size": { + "fixed": 5, + "max": 30, + "min": 2 + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed" + }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center" + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle" + } + } + }, + "location": { + "gazetteer": "public/gazetteer/countries.json", + "mode": "lookup" + }, + "name": "Layer 0", + "tooltip": true, + "type": "markers" + } + ], + "tooltip": { + "mode": "details" + }, + "view": { + "allLayers": true, + "id": "zero", + "lat": 0, + "lon": 0, + "zoom": 1 + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-27": { + "kind": "Panel", + "spec": { + "id": 27, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "11.0.0-pre", + "spec": { + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# grafana-worldmap-panel \u003e\u003e geomap\n\nKnown issues:\n* TBD", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-28": { + "kind": "Panel", + "spec": { + "id": 28, + "title": "Flot graph - x axis series mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": { + "rawFrameContent": "[\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ],\n \"custom\": {\n \"customStat\": 10\n }\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 3600000\n }\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {\n \"pod\": \"A-pod\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1727107111901,\n 1727110711901,\n 1727114311901,\n 1727117911901,\n 1727121511901,\n 1727125111901\n ],\n [\n 1.907286825122581,\n 2.260951647569786,\n 1.887442338051216,\n 2.1526144400893514,\n 1.7287721375237766,\n 1.7262902137793208\n ]\n ]\n }\n },\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ],\n \"custom\": {\n \"customStat\": 10\n }\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 3600000\n }\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {\n \"pod\": \"A-pod1\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1727107111901,\n 1727110711901,\n 1727114311901,\n 1727117911901,\n 1727121511901,\n 1727125111901\n ],\n [\n 1.907286825122581,\n 1.589539045095202,\n 1.5914283506847613,\n 1.8976990616650726,\n 1.758223085999124,\n 2.2294649594813816\n ]\n ]\n }\n },\n {\n \"schema\": {\n \"refId\": \"A\",\n \"meta\": {\n \"typeVersion\": [\n 0,\n 0\n ],\n \"custom\": {\n \"customStat\": 10\n }\n },\n \"fields\": [\n {\n \"name\": \"time\",\n \"type\": \"time\",\n \"typeInfo\": {\n \"frame\": \"time.Time\",\n \"nullable\": true\n },\n \"config\": {\n \"interval\": 3600000\n }\n },\n {\n \"name\": \"Value\",\n \"type\": \"number\",\n \"typeInfo\": {\n \"frame\": \"float64\",\n \"nullable\": true\n },\n \"labels\": {\n \"pod\": \"A-pod2\"\n },\n \"config\": {}\n }\n ]\n },\n \"data\": {\n \"values\": [\n [\n 1727107111901,\n 1727110711901,\n 1727114311901,\n 1727117911901,\n 1727121511901,\n 1727125111901\n ],\n [\n 1.907286825122581,\n 2.0914263380328766,\n 1.8164545521094575,\n 1.621111084665713,\n 1.3902653996444705,\n 1.482803315949775\n ]\n ]\n }\n }\n]", + "scenarioId": "raw_frame" + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "barchart", + "version": "11.0.0-pre", + "spec": { + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:88", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:89", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + } + } + } + } + }, + "panel-29": { + "kind": "Panel", + "spec": { + "id": 29, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "11.0.0-pre", + "spec": { + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Bar chart panel\n", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-30": { + "kind": "Panel", + "spec": { + "id": 30, + "title": "Flot graph - x axis histogram mode", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": { + "scenarioId": "random_walk", + "seriesCount": 3 + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "histogram", + "version": "11.0.0-pre", + "spec": { + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "histogram", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:193", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:194", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + } + } + } + } + }, + "panel-31": { + "kind": "Panel", + "spec": { + "id": 31, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "11.0.0-pre", + "spec": { + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Histogram panel\n", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-32": { + "kind": "Panel", + "spec": { + "id": 32, + "title": "Flot graph - x axis series mode (with legend calcs)", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "datasource", + "version": "v0", + "datasource": { + "name": "-- Dashboard --" + }, + "spec": { + "panelId": 28 + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "bargauge", + "version": "11.3.0-pre", + "spec": { + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "series", + "show": true, + "values": [ + "total" + ] + }, + "yaxes": [ + { + "$$hashKey": "object:88", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:89", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + } + } + } + } + }, + "panel-33": { + "kind": "Panel", + "spec": { + "id": 33, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "11.3.0-pre", + "spec": { + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Bar gauge panel\n", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-4": { + "kind": "Panel", + "spec": { + "id": 4, + "title": "Flot graph", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": { + "scenarioId": "random_walk", + "seriesCount": 3 + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "11.0.0-pre", + "spec": { + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "fill": 1, + "fillGradient": 0, + "hiddenSeries": false, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "thresholds": [], + "timeRegions": [], + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "logBase": 1, + "show": true + }, + { + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + }, + "alertThreshold": true, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + } + } + } + } + }, + "panel-6": { + "kind": "Panel", + "spec": { + "id": 6, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "11.0.0-pre", + "spec": { + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-7": { + "kind": "Panel", + "spec": { + "id": 7, + "title": "Status + Notes", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "text", + "version": "11.0.0-pre", + "spec": { + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Table (old) \u003e\u003e Table\n\nKnown issues:\n* wrapping text\n* style changes", + "mode": "markdown" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + }, + "panel-9": { + "kind": "Panel", + "spec": { + "id": 9, + "title": "grafana-singlestat-panel", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana-testdata-datasource", + "version": "v0", + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": { + "maxDataPoints": 100 + } + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "stat", + "version": "", + "spec": { + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-singlestat-panel", + "originalOptions": { + "colorBackground": false, + "colorValue": true, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "format": "areaF2", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "nullPointMode": "connected", + "postfix": "b", + "postfixFontSize": "50%", + "prefix": "a", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": true + }, + "tableColumn": "", + "thresholds": "", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + } + } + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 16, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-4" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 0, + "width": 8, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-6" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 11, + "width": 16, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-28" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 11, + "width": 8, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-29" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 22, + "width": 16, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-32" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 22, + "width": 16, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-30" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 22, + "width": 8, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-33" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 22, + "width": 8, + "height": 11, + "element": { + "kind": "ElementReference", + "name": "panel-31" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 33, + "width": 16, + "height": 10, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 33, + "width": 8, + "height": 10, + "element": { + "kind": "ElementReference", + "name": "panel-7" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 43, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-9" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 8, + "y": 43, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-23" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 43, + "width": 8, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-10" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 51, + "width": 16, + "height": 10, + "element": { + "kind": "ElementReference", + "name": "panel-24" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 51, + "width": 8, + "height": 10, + "element": { + "kind": "ElementReference", + "name": "panel-25" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 61, + "width": 16, + "height": 10, + "element": { + "kind": "ElementReference", + "name": "panel-26" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 16, + "y": 61, + "width": 8, + "height": 10, + "element": { + "kind": "ElementReference", + "name": "panel-27" + } + } + } + ] + } + }, + "links": [ + { + "title": "Auto migrate graph panel (TRUE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateGraphPanel=true", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate graph panel (FALSE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateGraphPanel=false", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate table (old) panel (TRUE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateTablePanel=true", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate table (old) panel (FALSE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateTablePanel=false", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate piechart panel (TRUE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigratePiechartPanel=true", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate piechart panel (FALSE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigratePiechartPanel=false", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate worldmap panel (TRUE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateWorldmapPanel=true", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate worldmap panel (FALSE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateWorldmapPanel=false", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate stat panel (TRUE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateStatPanel=true", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate stat panel (FALSE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": " /d/cdd412c4/?__feature.autoMigrateStatPanel=false", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate (TRUE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": "/d/cdd412c4/?__feature.autoMigrateOldPanels=true", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Auto migrate (FALSE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": "/d/cdd412c4/?__feature.autoMigrateOldPanels=false", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Disable angular (TRUE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": "/d/cdd412c4/?__feature.disableAngular=true", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + }, + { + "title": "Disable angular (FALSE)", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": "/d/cdd412c4/?__feature.disableAngular=false", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + } + ], + "liveNow": false, + "preload": false, + "tags": [ + "gdev", + "migrations", + "angular" + ], + "timeSettings": { + "timezone": "", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "Devenv - Panel migrations", + "variables": [] + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2alpha1.json index ae10c4f3807..f1004780fb0 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2alpha1.json @@ -181,7 +181,12 @@ "kind": "timeseries", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2beta1.json index 910dbbe3e1c..84064324429 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.panel-conversions.v2beta1.json @@ -186,7 +186,12 @@ "group": "timeseries", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "graph", + "originalOptions": {} + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go index 1135927ed7b..81c52bcc43e 100644 --- a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go +++ b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go @@ -2196,6 +2196,32 @@ func transformDataLinks(panelMap map[string]interface{}) []dashv2alpha1.Dashboar return result } +// knownPanelProperties lists all properties defined in the Panel schema (dashboard_kind.cue) +// that should NOT be passed to Angular migration handlers. Only "unknown" Angular-specific +// properties should be passed to migration handlers. +var knownPanelProperties = map[string]bool{ + "type": true, "id": true, "pluginVersion": true, "targets": true, + "title": true, "description": true, "transparent": true, "datasource": true, + "gridPos": true, "links": true, "repeat": true, "repeatDirection": true, + "maxPerRow": true, "maxDataPoints": true, "transformations": true, + "interval": true, "timeFrom": true, "timeShift": true, "hideTimeOverride": true, + "timeCompare": true, "libraryPanel": true, "cacheTimeout": true, + "queryCachingTTL": true, "options": true, "fieldConfig": true, "autoMigrateFrom": true, +} + +// extractAngularOptions extracts only the Angular-specific options from a panel map, +// filtering out all known Panel schema properties. This is used to pass just the +// Angular options to migration handlers (e.g., sparkline, valueName, format for singlestat). +func extractAngularOptions(panelMap map[string]interface{}) map[string]interface{} { + result := make(map[string]interface{}) + for key, value := range panelMap { + if !knownPanelProperties[key] { + result[key] = value + } + } + return result +} + func buildVizConfig(panelMap map[string]interface{}) dashv2alpha1.DashboardVizConfigKind { panelType := schemaversion.GetStringValue(panelMap, "type", "timeseries") pluginVersion := schemaversion.GetStringValue(panelMap, "pluginVersion") @@ -2208,7 +2234,10 @@ func buildVizConfig(panelMap map[string]interface{}) dashv2alpha1.DashboardVizCo options := make(map[string]interface{}) if opts, ok := panelMap["options"].(map[string]interface{}); ok { - options = opts + // Deep copy options to avoid modifying the original + for k, v := range opts { + options[k] = v + } } // Add frontend-style default options to match frontend behavior @@ -2219,11 +2248,33 @@ func buildVizConfig(panelMap map[string]interface{}) dashv2alpha1.DashboardVizCo options["legend"] = legend } + // Handle Angular panel migrations + // This replicates the v0→v1 migration logic for panels that weren't migrated yet. + // We check two cases: + // 1. Panel already has autoMigrateFrom set (from v0→v1 migration) - panel type already converted + // 2. Panel type is a known Angular panel - need to convert type AND set autoMigrateFrom + autoMigrateFrom, hasAutoMigrateFrom := panelMap["autoMigrateFrom"].(string) + + if !hasAutoMigrateFrom || autoMigrateFrom == "" { + // Check if panel type is an Angular type that needs migration + if newType := getAngularPanelMigration(panelType, panelMap); newType != "" { + autoMigrateFrom = panelType // Original Angular type + panelType = newType // New modern type + } + } + + if autoMigrateFrom != "" { + options["__angularMigration"] = map[string]interface{}{ + "autoMigrateFrom": autoMigrateFrom, + "originalOptions": extractAngularOptions(panelMap), + } + } + // Build field config by mapping each field individually fieldConfigSource := extractFieldConfigSource(fieldConfig) return dashv2alpha1.DashboardVizConfigKind{ - Kind: panelType, // Use panelType as Kind (plugin ID) to match schema comment + Kind: panelType, // Use panelType as Kind (plugin ID) - may be converted from Angular type Spec: dashv2alpha1.DashboardVizConfigSpec{ PluginVersion: pluginVersion, FieldConfig: fieldConfigSource, @@ -2664,3 +2715,10 @@ func extractFieldConfigOverrides(fieldConfig map[string]interface{}) []dashv2alp return result } + +// getAngularPanelMigration is a convenience wrapper around schemaversion.GetAngularPanelMigration. +// It checks if a panel type is an Angular panel and returns the new type to migrate to. +// Returns the new panel type if migration is needed, empty string otherwise. +func getAngularPanelMigration(panelType string, panelMap map[string]interface{}) string { + return schemaversion.GetAngularPanelMigration(panelType, panelMap) +} diff --git a/apps/dashboard/pkg/migration/frontend_defaults.go b/apps/dashboard/pkg/migration/frontend_defaults.go index 1df28c4de27..e8c779406a6 100644 --- a/apps/dashboard/pkg/migration/frontend_defaults.go +++ b/apps/dashboard/pkg/migration/frontend_defaults.go @@ -940,31 +940,12 @@ func applyPanelAutoMigration(panel map[string]interface{}) { return } - var newType string - // Graph needs special logic as it can be migrated to multiple panels // Including graphite which was previously migrated to graph in the schema version 2 migration in DashboardMigrator.ts // but this was a bug because in there graphite was set to graph, but since those migrations run // after PanelModel.restoreModel where autoMigrateFrom is set, this caused the graph migration to be skipped. // And this resulted in a dashboard with invalid panels. - if panelType == "graph" || panelType == "graphite" { - // Check xaxis mode for special cases - newType = getGraphAutoMigration(panel) - } else { - // Check autoMigrateAngular mapping - autoMigrateAngular := map[string]string{ - "table-old": "table", - "singlestat": "stat", - "grafana-singlestat-panel": "stat", - "grafana-piechart-panel": "piechart", - "grafana-worldmap-panel": "geomap", - "natel-discrete-panel": "state-timeline", - } - - if mappedType, exists := autoMigrateAngular[panelType]; exists { - newType = mappedType - } - } + newType := schemaversion.GetAngularPanelMigration(panelType, panel) // Apply auto-migration if a new type was determined if newType != "" { @@ -973,36 +954,6 @@ func applyPanelAutoMigration(panel map[string]interface{}) { } } -func getGraphAutoMigration(panel map[string]interface{}) string { - newType := "" - if xaxis, ok := panel["xaxis"].(map[string]interface{}); ok { - if mode, ok := xaxis["mode"].(string); ok { - switch mode { - case "series": - // Check legend values for bargauge - if legend, ok := panel["legend"].(map[string]interface{}); ok { - if values, ok := legend["values"].(bool); ok && values { - newType = "bargauge" - } else { - newType = "barchart" - } - } else { - newType = "barchart" - } - case "histogram": - newType = "histogram" - } - } - } - - // Default graph migration to timeseries - if newType == "" { - newType = "timeseries" - } - - return newType -} - // removeNullValuesRecursively removes null values from nested objects and arrays // This matches the frontend's JSON.stringify/parse behavior func removeNullValuesRecursively(data interface{}) { diff --git a/apps/dashboard/pkg/migration/schemaversion/migration_utils.go b/apps/dashboard/pkg/migration/schemaversion/migration_utils.go index 64dfc61589e..d964fcaf513 100644 --- a/apps/dashboard/pkg/migration/schemaversion/migration_utils.go +++ b/apps/dashboard/pkg/migration/schemaversion/migration_utils.go @@ -102,3 +102,71 @@ func IsArray(value interface{}) bool { _, ok := value.([]interface{}) return ok } + +// AngularPanelMigrations maps deprecated Angular panel types to their modern equivalents. +// Used by both v0→v1 and v1→v2 conversions to ensure consistent migration behavior. +var AngularPanelMigrations = map[string]string{ + "table-old": "table", + "singlestat": "stat", + "grafana-singlestat-panel": "stat", + "grafana-piechart-panel": "piechart", + "grafana-worldmap-panel": "geomap", + "natel-discrete-panel": "state-timeline", +} + +// GetAngularPanelMigration checks if a panel type is an Angular panel and returns the new type to migrate to. +// Returns the new panel type if migration is needed, empty string otherwise. +// This handles both simple mappings and special cases like graph panel. +func GetAngularPanelMigration(panelType string, panel map[string]interface{}) string { + // Handle graph panel specially - it can migrate to different panel types + // based on xaxis.mode + if panelType == "graph" || panelType == "graphite" { + return GetGraphMigrationTarget(panel) + } + + // Check simple Angular panel mappings + if newType, isAngular := AngularPanelMigrations[panelType]; isAngular { + return newType + } + + return "" +} + +// GetGraphMigrationTarget determines the target panel type for graph panel migration. +// Graph panels can migrate to timeseries, barchart, bargauge, or histogram depending on xaxis.mode. +func GetGraphMigrationTarget(panel map[string]interface{}) string { + // Default to timeseries + newType := "timeseries" + + // Check xaxis mode for special cases + if xaxis, ok := panel["xaxis"].(map[string]interface{}); ok { + if mode, ok := xaxis["mode"].(string); ok { + switch mode { + case "series": + // Check legend values for bargauge vs barchart + if legend, ok := panel["legend"].(map[string]interface{}); ok { + if values, ok := legend["values"].(bool); ok && values { + newType = "bargauge" + } else { + newType = "barchart" + } + } else { + newType = "barchart" + } + case "histogram": + newType = "histogram" + } + } + } + + return newType +} + +// IsAngularPanelType checks if a panel type is a known Angular panel type. +func IsAngularPanelType(panelType string) bool { + if panelType == "graph" || panelType == "graphite" { + return true + } + _, isAngular := AngularPanelMigrations[panelType] + return isAngular +} diff --git a/kinds/dashboard/dashboard_kind.cue b/kinds/dashboard/dashboard_kind.cue index 454d02f270f..08b7a1b2b6e 100644 --- a/kinds/dashboard/dashboard_kind.cue +++ b/kinds/dashboard/dashboard_kind.cue @@ -702,6 +702,10 @@ lineage: schemas: [{ // Field options allow you to change how the data is displayed in your visualizations. fieldConfig?: #FieldConfigSource + + // When a panel is migrated from a previous version (Angular to React), this field is set to the original panel type. + // This is used to determine the original panel type when migrating to a new version so the plugin migration can be applied. + autoMigrateFrom?: string } @cuetsy(kind="interface") @grafana(TSVeneer="type") @grafanamaturity(NeedsExpertReview) // The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results. diff --git a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts index 34aa9ac015b..f2423566ff5 100644 --- a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts +++ b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts @@ -838,6 +838,11 @@ export const defaultDashboardCursorSync: DashboardCursorSync = DashboardCursorSy * Dashboard panels are the basic visualization building blocks. */ export interface Panel { + /** + * When a panel is migrated from a previous version (Angular to React), this field is set to the original panel type. + * This is used to determine the original panel type when migrating to a new version so the plugin migration can be applied. + */ + autoMigrateFrom?: string; /** * Sets panel queries cache timeout. */ diff --git a/pkg/kinds/dashboard/dashboard_spec_gen.go b/pkg/kinds/dashboard/dashboard_spec_gen.go index bd73e527534..7d2a0ba5439 100644 --- a/pkg/kinds/dashboard/dashboard_spec_gen.go +++ b/pkg/kinds/dashboard/dashboard_spec_gen.go @@ -204,6 +204,9 @@ type Panel struct { Options map[string]any `json:"options,omitempty"` // Field options allow you to change how the data is displayed in your visualizations. FieldConfig *FieldConfigSource `json:"fieldConfig,omitempty"` + // When a panel is migrated from a previous version (Angular to React), this field is set to the original panel type. + // This is used to determine the original panel type when migrating to a new version so the plugin migration can be applied. + AutoMigrateFrom *string `json:"autoMigrateFrom,omitempty"` } // NewPanel creates a new Panel object. diff --git a/public/app/features/dashboard-scene/serialization/angularMigration.test.ts b/public/app/features/dashboard-scene/serialization/angularMigration.test.ts index a9f63b34cf0..46c67f6cd9f 100644 --- a/public/app/features/dashboard-scene/serialization/angularMigration.test.ts +++ b/public/app/features/dashboard-scene/serialization/angularMigration.test.ts @@ -2,7 +2,7 @@ import { PanelTypeChangedHandler } from '@grafana/data'; import { getPanelPlugin } from '@grafana/data/test'; import { PanelModel } from 'app/features/dashboard/state/PanelModel'; -import { getAngularPanelMigrationHandler } from './angularMigration'; +import { getV2AngularMigrationHandler, getAngularPanelMigrationHandler } from './angularMigration'; describe('getAngularPanelMigrationHandler', () => { describe('Given an old angular panel', () => { @@ -54,3 +54,172 @@ describe('getAngularPanelMigrationHandler', () => { }); }); }); + +describe('getV2AngularMigrationHandler', () => { + describe('Given v2 migration data for an old angular panel (singlestat)', () => { + it('Should call migration handler with angular wrapper for original options', () => { + let receivedPrevPluginId: string | undefined; + let receivedPrevOptions: Record | undefined; + + const onPanelTypeChanged: PanelTypeChangedHandler = (panel, prevPluginId, prevOptions) => { + receivedPrevPluginId = prevPluginId; + receivedPrevOptions = prevOptions; + panel.fieldConfig = { defaults: { unit: 'short' }, overrides: [] }; + return { reduceOptions: { calcs: ['mean'] }, orientation: 'horizontal' }; + }; + + const reactPlugin = getPanelPlugin({ id: 'stat' }).setPanelChangeHandler(onPanelTypeChanged); + + // originalOptions only contains Angular-specific options, not known Panel properties + const migrationData = { + autoMigrateFrom: 'singlestat', + originalOptions: { + format: 'short', + valueName: 'avg', + orientation: 'horizontal', + }, + }; + + const mutatedModel = { + id: 1, + type: 'stat', + options: {}, + fieldConfig: { defaults: {}, overrides: [] }, + }; + + getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + + // Verify handler received correct arguments + expect(receivedPrevPluginId).toBe('singlestat'); + expect(receivedPrevOptions?.angular).toBeDefined(); + expect((receivedPrevOptions?.angular as Record)?.format).toBe('short'); + expect((receivedPrevOptions?.angular as Record)?.valueName).toBe('avg'); + + // Verify mutation results + expect(mutatedModel.options).toEqual({ reduceOptions: { calcs: ['mean'] }, orientation: 'horizontal' }); + expect(mutatedModel.fieldConfig).toEqual({ defaults: { unit: 'short' }, overrides: [] }); + }); + }); + + describe('Given v2 migration data for graph panel', () => { + it('Should call migration handler with angular wrapper', () => { + let receivedPrevPluginId: string | undefined; + let receivedPrevOptions: Record | undefined; + + const onPanelTypeChanged: PanelTypeChangedHandler = (panel, prevPluginId, prevOptions) => { + receivedPrevPluginId = prevPluginId; + receivedPrevOptions = prevOptions; + return { drawStyle: 'bars' }; + }; + + const reactPlugin = getPanelPlugin({ id: 'timeseries' }).setPanelChangeHandler(onPanelTypeChanged); + + // originalOptions only contains Angular-specific options (bars, lines, etc.) + const migrationData = { + autoMigrateFrom: 'graph', + originalOptions: { + bars: true, + lines: false, + }, + }; + + const mutatedModel = { + id: 1, + type: 'timeseries', + options: {}, + fieldConfig: { defaults: {}, overrides: [] }, + }; + + getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + + // Verify handler received correct arguments + expect(receivedPrevPluginId).toBe('graph'); + expect(receivedPrevOptions?.angular).toBeDefined(); + expect((receivedPrevOptions?.angular as Record)?.bars).toBe(true); + + // Verify mutation results + expect(mutatedModel.options).toEqual({ drawStyle: 'bars' }); + }); + }); + + describe('Given v2 migration data for non-angular panel migration', () => { + it('Should pass options wrapper instead of angular wrapper', () => { + let receivedPrevPluginId: string | undefined; + let receivedPrevOptions: Record | undefined; + + const onPanelTypeChanged: PanelTypeChangedHandler = (panel, prevPluginId, prevOptions) => { + receivedPrevPluginId = prevPluginId; + receivedPrevOptions = prevOptions; + return { newOption: 'value' }; + }; + + const reactPlugin = getPanelPlugin({ id: 'new-panel' }).setPanelChangeHandler(onPanelTypeChanged); + + // For non-angular panels, originalOptions contains the panel-specific options + const migrationData = { + autoMigrateFrom: 'some-react-panel', // Not in autoMigrateAngular list + originalOptions: { + oldOption: 'old', + }, + }; + + const mutatedModel = { + id: 1, + type: 'new-panel', + options: {}, + fieldConfig: { defaults: {}, overrides: [] }, + }; + + getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + + // Verify handler received correct arguments (options wrapper, not angular) + expect(receivedPrevPluginId).toBe('some-react-panel'); + expect(receivedPrevOptions?.options).toBeDefined(); + expect(receivedPrevOptions?.angular).toBeUndefined(); + + // Verify mutation results + expect(mutatedModel.options).toEqual({ newOption: 'value' }); + }); + }); + + describe('targets property access during migration', () => { + it('Should proxy targets with deprecation warning', () => { + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(); + let accessedTargets: unknown; + + const onPanelTypeChanged: PanelTypeChangedHandler = (panel) => { + // Access targets during migration (deprecated but supported) + accessedTargets = panel.targets; + return {}; + }; + + const reactPlugin = getPanelPlugin({ id: 'stat' }).setPanelChangeHandler(onPanelTypeChanged); + + const migrationData = { + autoMigrateFrom: 'singlestat', + originalOptions: { format: 'short' }, + }; + + const originalTargets = [{ refId: 'A', expr: 'test' }]; + const mutatedModel = { + id: 1, + type: 'stat', + options: {}, + fieldConfig: { defaults: {}, overrides: [] }, + targets: originalTargets, + }; + + getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + + // Verify targets were accessible and returned the cloned value + expect(accessedTargets).toEqual(originalTargets); + + // Verify deprecation warning was logged + expect(warnSpy).toHaveBeenCalledWith( + 'Accessing the targets property when migrating a panel plugin is deprecated. Changes to this property will be ignored.' + ); + + warnSpy.mockRestore(); + }); + }); +}); diff --git a/public/app/features/dashboard-scene/serialization/angularMigration.ts b/public/app/features/dashboard-scene/serialization/angularMigration.ts index 81649cfadd3..54b47081080 100644 --- a/public/app/features/dashboard-scene/serialization/angularMigration.ts +++ b/public/app/features/dashboard-scene/serialization/angularMigration.ts @@ -3,6 +3,31 @@ import { defaults, cloneDeep } from 'lodash'; import { PanelModel as PanelModelFromData, PanelPlugin } from '@grafana/data'; import { autoMigrateAngular, PanelModel } from 'app/features/dashboard/state/PanelModel'; +/** + * Data structure for Angular migration information stored in v2 schema options. + */ +export interface AngularMigrationData { + /** The original panel type before migration (e.g., "singlestat", "graph") */ + autoMigrateFrom: string; + /** Angular-specific options not part of the Panel schema */ + originalOptions: Record; +} + +/** + * Type guard to check if an unknown value is AngularMigrationData. + */ +export function isAngularMigrationData(value: unknown): value is AngularMigrationData { + if (typeof value !== 'object' || value === null) { + return false; + } + if (!('autoMigrateFrom' in value) || !('originalOptions' in value)) { + return false; + } + + const { autoMigrateFrom, originalOptions } = value; + return typeof autoMigrateFrom === 'string' && typeof originalOptions === 'object'; +} + export function getAngularPanelMigrationHandler(oldModel: PanelModel) { return function handleAngularPanelMigrations(panel: PanelModelFromData, plugin: PanelPlugin) { if ('angularPanelCtrl' in plugin && plugin.angularPanelCtrl) { @@ -36,3 +61,52 @@ export function getAngularPanelMigrationHandler(oldModel: PanelModel) { } }; } + +/** + * Returns a migration handler for v2 schema panels that need Angular migrations. + * + * This is used when loading v2 dashboards that were converted from v1 and contain + * __angularMigration data. The handler invokes the plugin's onPanelTypeChanged + * to properly migrate options from the original Angular panel format. + * + * Example flow for singlestat -> stat: + * 1. v0 -> v1 migration converts panel type from "singlestat" to "stat" and sets autoMigrateFrom="singlestat" + * (OR if dashboard was stored directly as v1 with "singlestat", the v1 -> v2 conversion does this) + * 2. v1 -> v2 conversion detects autoMigrateFrom and stores Angular-specific options in options.__angularMigration + * 3. Frontend loads v2 dashboard, extracts __angularMigration, and attaches this handler + * 4. Handler calls stat plugin's onPanelTypeChanged with { angular: originalOptions } + * 5. Plugin migrates format/valueName/etc to proper stat options + * + * @param migrationData The __angularMigration data extracted from panel options + */ +export function getV2AngularMigrationHandler(migrationData: AngularMigrationData) { + return function handleV2AngularMigration(panel: PanelModelFromData, plugin: PanelPlugin) { + const { autoMigrateFrom, originalOptions } = migrationData; + const wasAngular = autoMigrateAngular[autoMigrateFrom] != null; + + // Handle plugins that still use angularPanelCtrl + if ('angularPanelCtrl' in plugin && plugin.angularPanelCtrl) { + panel.options = { angularOptions: originalOptions }; + return; + } + + if (plugin.onPanelTypeChanged) { + // Some plugins rely on being able to access targets to set up the fieldConfig when migrating from angular. + // Proxy the targets property with a deprecation warning. + const targetClone = cloneDeep(panel.targets); + Object.defineProperty(panel, 'targets', { + get: function () { + console.warn( + 'Accessing the targets property when migrating a panel plugin is deprecated. Changes to this property will be ignored.' + ); + return targetClone; + }, + }); + + // For Angular panels, wrap in { angular: ... } to match expected format + // For React panels migrating from other React panels, pass options directly + const prevOptions = wasAngular ? { angular: originalOptions } : { options: originalOptions }; + Object.assign(panel.options, plugin.onPanelTypeChanged(panel, autoMigrateFrom, prevOptions, panel.fieldConfig)); + } + }; +} diff --git a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts index 6595268025d..2256206aaee 100644 --- a/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts +++ b/public/app/features/dashboard-scene/serialization/layoutSerializers/utils.ts @@ -40,6 +40,7 @@ import { PanelTimeRange } from '../../scene/panel-timerange/PanelTimeRange'; import { setDashboardPanelContext } from '../../scene/setDashboardPanelContext'; import { DashboardLayoutManager } from '../../scene/types/DashboardLayoutManager'; import { getVizPanelKeyForPanelId } from '../../utils/utils'; +import { getV2AngularMigrationHandler, isAngularMigrationData } from '../angularMigration'; import { createElements, vizPanelToSchemaV2 } from '../transformSceneToSaveModelSchemaV2'; import { transformMappingsToV1 } from '../transformToV1TypesUtils'; import { transformDataTopic } from '../transformToV2TypesUtils'; @@ -59,12 +60,22 @@ export function buildVizPanel(panel: PanelKind, id?: number): VizPanel { const queryOptions = panel.spec.data.spec.queryOptions; const timeOverrideShown = (queryOptions.timeFrom || queryOptions.timeShift) && !queryOptions.hideTimeOverride; + // Extract __angularMigration data if present + // This data is used to run Angular panel migrations in v2 (e.g., singlestat -> stat) + const rawOptions = panel.spec.vizConfig.spec.options ?? {}; + const rawAngularMigration = rawOptions.__angularMigration; + const angularMigration = isAngularMigrationData(rawAngularMigration) ? rawAngularMigration : undefined; + + // Create clean options without __angularMigration (it's only for migration, not for the panel) + const options = { ...rawOptions }; + delete options.__angularMigration; + const vizPanelState: VizPanelState = { key: getVizPanelKeyForPanelId(id ?? panel.spec.id), title: panel.spec.title?.substring(0, 5000), description: panel.spec.description, pluginId: panel.spec.vizConfig.group, - options: panel.spec.vizConfig.spec.options, + options, fieldConfig: transformMappingsToV1(panel.spec.vizConfig.spec.fieldConfig), pluginVersion: panel.spec.vizConfig.version, displayMode: panel.spec.transparent ? 'transparent' : 'default', @@ -83,6 +94,12 @@ export function buildVizPanel(panel: PanelKind, id?: number): VizPanel { extendPanelContext: setDashboardPanelContext, }; + // Set up Angular migration handler if migration data is present + // This enables proper migration of options from Angular panels (e.g., singlestat format/valueName) + if (angularMigration) { + vizPanelState._UNSAFE_customMigrationHandler = getV2AngularMigrationHandler(angularMigration); + } + if (!config.publicDashboardAccessToken) { vizPanelState.menu = new VizPanelMenu({ $behaviors: [panelMenuBehavior], diff --git a/public/app/features/dashboard/api/ResponseTransformers.ts b/public/app/features/dashboard/api/ResponseTransformers.ts index d2640991b1e..5a536074efe 100644 --- a/public/app/features/dashboard/api/ResponseTransformers.ts +++ b/public/app/features/dashboard/api/ResponseTransformers.ts @@ -528,6 +528,56 @@ export function getPanelQueries(targets: DataQuery[], panelDatasource: DataSourc }); } +/** + * Known Panel properties from the Panel schema (dashboard_kind.cue). + * These should NOT be passed to Angular migration handlers. + * Only "unknown" Angular-specific properties should be passed. + */ +const knownPanelProperties = new Set([ + 'type', + 'id', + 'pluginVersion', + 'targets', + 'title', + 'description', + 'transparent', + 'datasource', + 'gridPos', + 'links', + 'repeat', + 'repeatDirection', + 'maxPerRow', + 'maxDataPoints', + 'transformations', + 'interval', + 'timeFrom', + 'timeShift', + 'hideTimeOverride', + 'timeCompare', + 'libraryPanel', + 'cacheTimeout', + 'queryCachingTTL', + 'options', + 'fieldConfig', + 'autoMigrateFrom', +]); + +/** + * Extracts only the Angular-specific options from a panel, + * filtering out all known Panel schema properties. + * This is used to pass just the Angular options to migration handlers + * (e.g., sparkline, valueName, format for singlestat). + */ +function extractAngularOptions(panel: Panel): Record { + const result: Record = {}; + for (const [key, value] of Object.entries(panel)) { + if (!knownPanelProperties.has(key)) { + result[key] = value; + } + } + return result; +} + export function buildPanelKind(p: Panel): PanelKind { // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions const queries = getPanelQueries((p.targets as any) || [], p.datasource ?? { type: '', uid: '' }); @@ -557,6 +607,23 @@ export function buildPanelKind(p: Panel): PanelKind { fieldConfig.defaults.thresholds.steps[0]!.value = null; } + // Build options with Angular migration data if needed (matches backend behavior) + // autoMigrateFrom is set during v0->v1 migration when Angular panels are converted + const { autoMigrateFrom } = p; + let options = p.options ?? {}; + + // When autoMigrateFrom is present, compose __angularMigration with only Angular-specific options + // This filters out known Panel schema properties, passing only the Angular options to migration handlers + if (autoMigrateFrom) { + options = { + ...options, + __angularMigration: { + autoMigrateFrom, + originalOptions: extractAngularOptions(p), + }, + }; + } + const panelKind: PanelKind = { kind: 'Panel', spec: { @@ -569,7 +636,7 @@ export function buildPanelKind(p: Panel): PanelKind { version: p.pluginVersion ?? '', spec: { fieldConfig: p.fieldConfig || defaultFieldConfigSource(), - options: p.options ?? {}, + options, }, }, links: diff --git a/public/app/features/dashboard/api/ResponseTransformersToBackend.test.ts b/public/app/features/dashboard/api/ResponseTransformersToBackend.test.ts index 45a310403e4..2ca8d2a2411 100644 --- a/public/app/features/dashboard/api/ResponseTransformersToBackend.test.ts +++ b/public/app/features/dashboard/api/ResponseTransformersToBackend.test.ts @@ -1,12 +1,13 @@ import { readdirSync, readFileSync } from 'fs'; import path from 'path'; -import { mockDataSource } from 'app/features/alerting/unified/mocks'; -import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources'; +import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { normalizeBackendOutputForFrontendComparison } from 'app/features/dashboard-scene/serialization/serialization-test-utils'; -import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; +import { transformSaveModelSchemaV2ToScene } from 'app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene'; +import { transformSaveModelToScene } from 'app/features/dashboard-scene/serialization/transformSaveModelToScene'; +import { transformSceneToSaveModelSchemaV2 } from 'app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2'; -import { ensureV2Response } from './ResponseTransformers'; +import { DashboardWithAccessInfo } from './types'; // Mock the config to provide datasource information jest.mock('@grafana/runtime', () => { @@ -14,46 +15,87 @@ jest.mock('@grafana/runtime', () => { ...jest.requireActual('@grafana/runtime').config, defaultDatasource: 'default-ds-uid', datasources: { - 'default-ds-uid': { - meta: { id: 'prometheus' }, - name: 'default-ds-uid', - }, - 'non-default-test-ds-uid': { - meta: { id: 'loki' }, - name: 'non-default-test-ds-uid', + '-- Grafana --': { + type: 'grafana', + uid: '-- Grafana --', + name: 'Grafana', + meta: { id: 'grafana' }, }, 'existing-ref-uid': { + type: 'prometheus', + uid: 'existing-ref-uid', + name: 'Prometheus', meta: { id: 'prometheus' }, - name: 'existing-ref-uid', }, - 'existing-target-uid': { - meta: { id: 'elasticsearch' }, - name: 'existing-target-uid', - }, - 'existing-ref': { - meta: { id: 'prometheus' }, - name: 'existing-ref', - }, - '-- Mixed --': { - meta: { id: 'mixed' }, - name: '-- Mixed --', - }, - 'influx-uid': { + 'influxdb-uid': { + type: 'influxdb', + uid: 'influxdb-uid', + name: 'InfluxDB', meta: { id: 'influxdb' }, - name: 'influx-uid', }, 'cloudwatch-uid': { + type: 'cloudwatch', + uid: 'cloudwatch-uid', + name: 'CloudWatch', meta: { id: 'cloudwatch' }, - name: 'cloudwatch-uid', }, - '-- Grafana --': { - meta: { id: 'grafana' }, - name: '-- Grafana --', + 'elasticsearch-uid': { + type: 'elasticsearch', + uid: 'elasticsearch-uid', + name: 'Elasticsearch', + meta: { id: 'elasticsearch' }, + }, + 'loki-uid': { + type: 'loki', + uid: 'loki-uid', + name: 'Loki', + meta: { id: 'loki' }, + }, + 'default-ds-uid': { + type: 'prometheus', + uid: 'default-ds-uid', + name: 'Default Prometheus', + meta: { id: 'prometheus' }, + }, + 'existing-target-uid': { + type: 'elasticsearch', + uid: 'existing-target-uid', + name: 'Elasticsearch Target', + meta: { id: 'elasticsearch' }, + }, + 'non-default-test-ds-uid': { + type: 'loki', + uid: 'non-default-test-ds-uid', + name: 'Loki Test', + meta: { id: 'loki' }, + }, + '-- Mixed --': { + type: 'mixed', + uid: '-- Mixed --', + name: '-- Mixed --', + meta: { id: 'mixed' }, + }, + 'influx-uid': { + type: 'influxdb', + uid: 'influx-uid', + name: 'InfluxDB Test', + meta: { id: 'influxdb' }, + }, + 'cloudwatch-uid-alt': { + type: 'cloudwatch', + uid: 'cloudwatch-uid', + name: 'CloudWatch Test', + meta: { id: 'cloudwatch' }, + }, + 'existing-ref': { + type: 'prometheus', + uid: 'existing-ref', + name: 'Existing Ref Name', + meta: { id: 'prometheus' }, }, }, - apps: {}, featureToggles: { - dashboardScene: true, + dashboardNewLayouts: true, kubernetesDashboards: true, }, }; @@ -65,79 +107,20 @@ jest.mock('@grafana/runtime', () => { }); /* - * Frontend Conversion Test Design Explanation: + * V1 to V2 Dashboard Transformation Comparison Test (via ResponseTransformers) * - * This test verifies that the frontend ensureV2Response function correctly converts - * dashboard data to v2beta1 format. The test uses input files from the backend test suite - * and compares the frontend conversion results with expected backend conversion results. + * This test compares frontend and backend transformations of dashboard data from v1 to v2 format. + * It uses the same test data as the backend conversion tests and verifies that the frontend + * transformation produces equivalent results to the backend transformation. * - * Note: The frontend ensureV2Response function is designed to convert legacy dashboard - * format to v2 format, while the backend handles Kubernetes resource format conversions. - * This test focuses on verifying the frontend conversion logic works correctly. + * The test follows the same approach as transformSaveModelV1ToV2.test.ts: + * - Frontend path: v1beta1 spec -> Scene -> v2beta1 + * - Backend path: v2beta1 output -> Scene -> v2beta1 (normalized) */ -// Set up the same datasources as backend test provider to ensure consistency -const dataSources = { - default: mockDataSource({ - name: 'default-ds-uid', - uid: 'default-ds-uid', - type: 'prometheus', - isDefault: true, - }), - nonDefault: mockDataSource({ - name: 'Non Default Test Datasource Name', - uid: 'non-default-test-ds-uid', - type: 'loki', - isDefault: false, - }), - existingRef: mockDataSource({ - name: 'Existing Ref Name', - uid: 'existing-ref-uid', - type: 'prometheus', - isDefault: false, - }), - existingTarget: mockDataSource({ - name: 'Existing Target Name', - uid: 'existing-target-uid', - type: 'elasticsearch', - isDefault: false, - }), - existingRefAlt: mockDataSource({ - name: 'Existing Ref Name', - uid: 'existing-ref', - type: 'prometheus', - isDefault: false, - }), - mixed: mockDataSource({ - name: MIXED_DATASOURCE_NAME, - uid: '-- Mixed --', - type: 'mixed', - isDefault: false, - }), - influx: mockDataSource({ - name: 'InfluxDB Test', - uid: 'influx-uid', - type: 'influxdb', - isDefault: false, - }), - cloudwatch: mockDataSource({ - name: 'CloudWatch Test', - uid: 'cloudwatch-uid', - type: 'cloudwatch', - isDefault: false, - }), - grafana: mockDataSource({ - name: '-- Grafana --', - uid: '-- Grafana --', - type: 'grafana', - isDefault: false, - }), -}; - -describe('Backend / Frontend result comparison', () => { +describe('V1 to V2 Dashboard Transformation Comparison (ResponseTransformers)', () => { beforeEach(() => { jest.clearAllMocks(); - setupDataSources(...Object.values(dataSources)); // Mock console methods to avoid test failures from expected warnings jest.spyOn(console, 'error').mockImplementation(() => {}); @@ -182,48 +165,67 @@ describe('Backend / Frontend result comparison', () => { const v1beta1Inputs = jsonInputs.filter((inputFile) => inputFile.startsWith('v1beta1.')); v1beta1Inputs.forEach((inputFile) => { - it(`should convert ${inputFile} spec to match backend conversion`, async () => { + it(`compare ${inputFile} from v1beta1 to v2beta1 backend and frontend conversions`, async () => { const jsonInput = JSON.parse(readFileSync(path.join(inputDir, inputFile), 'utf8')); // Find the corresponding v2beta1 output file const outputFileName = inputFile.replace('.json', `.${LATEST_API_VERSION.split('/')[1]}.json`); const outputFilePath = path.join(outputDir, outputFileName); - // Check if the expected output file exists - try { - const backendOutput = JSON.parse(readFileSync(outputFilePath, 'utf8')); - expect(backendOutput.apiVersion).toBe(LATEST_API_VERSION); + const backendOutput = JSON.parse(readFileSync(outputFilePath, 'utf8')); + expect(backendOutput.apiVersion).toBe(LATEST_API_VERSION); - // Create dashboard models using frontend conversion - // ensureV2Response expects DashboardWithAccessInfo object - const frontendOutput = ensureV2Response({ - ...jsonInput, - kind: 'DashboardWithAccessInfo', - access: {}, - }); + // Backend path: Load backend output into Scene, then serialize back to v2beta1 + // This normalizes the backend output through the same Scene + const sceneBackend = transformSaveModelSchemaV2ToScene({ + spec: backendOutput.spec, + metadata: backendOutput.metadata, + apiVersion: backendOutput.apiVersion, + access: {}, + kind: 'DashboardWithAccessInfo', + } as DashboardWithAccessInfo); + const backendOutputAfterLoadedByScene = transformSceneToSaveModelSchemaV2(sceneBackend, false); - // Verify both outputs have valid spec structures - expect(frontendOutput.spec).toBeDefined(); - expect(backendOutput.spec).toBeDefined(); + // Frontend path: v1beta1 spec -> Scene -> v2beta1 + // Extract the spec from v1beta1 format and use it as the dashboard data + // Remove snapshot field to prevent isSnapshot() from returning true + const dashboardSpec = { ...jsonInput.spec }; + delete dashboardSpec.snapshot; - // Normalize backend output to account for differences in library panel repeat handling - // Backend sets repeat from library panel definition, frontend only sets it when explicit on instance - const inputPanels = jsonInput.spec?.panels || []; - const normalizedBackendSpec = normalizeBackendOutputForFrontendComparison(backendOutput.spec, inputPanels); + // Wrap in DashboardDTO structure that transformSaveModelToScene expects + const scene = transformSaveModelToScene({ + dashboard: dashboardSpec, + meta: { + isNew: false, + isFolder: false, + canSave: true, + canEdit: true, + canDelete: false, + canShare: false, + canStar: false, + canAdmin: false, + isSnapshot: false, + provisioned: false, + version: 1, + }, + }); - // Compare the spec structures - expect(normalizedBackendSpec).toEqual(frontendOutput.spec); + const frontendOutput = transformSceneToSaveModelSchemaV2(scene, false); - // Verify the conversion doesn't throw errors and produces a valid structure - expect(() => JSON.stringify(frontendOutput)).not.toThrow(); - } catch (error) { - if (error instanceof Error && error.message.includes('ENOENT')) { - // Skip test if output file doesn't exist - console.warn(`Skipping test for ${inputFile} - no corresponding v2beta1 output file found`); - return; - } - throw error; - } + // Verify both outputs have valid spec structures + expect(frontendOutput).toBeDefined(); + expect(backendOutputAfterLoadedByScene).toBeDefined(); + + // Normalize backend output to account for differences in library panel repeat handling + // Backend sets repeat from library panel definition, frontend only sets it when explicit on instance + const inputPanels = jsonInput.spec?.panels || []; + const normalizedBackendOutput = normalizeBackendOutputForFrontendComparison( + backendOutputAfterLoadedByScene, + inputPanels + ); + + // Compare the spec structures + expect(normalizedBackendOutput).toEqual(frontendOutput); }); }); }); diff --git a/public/app/features/dashboard/state/DashboardMigratorSingleVersion.test.ts b/public/app/features/dashboard/state/DashboardMigratorSingleVersion.test.ts index 33d9a339e62..f6f2fbe2be6 100644 --- a/public/app/features/dashboard/state/DashboardMigratorSingleVersion.test.ts +++ b/public/app/features/dashboard/state/DashboardMigratorSingleVersion.test.ts @@ -127,7 +127,6 @@ describe('Backend / Frontend single version migration result comparison', () => for (const nestedPanel of panel.panels) { const panelPluginToMigrateTo = getPanelPluginToMigrateTo(nestedPanel); if (panelPluginToMigrateTo) { - // @ts-expect-error - we are using the type from the frontend migration result nestedPanel.autoMigrateFrom = nestedPanel.type; nestedPanel.type = panelPluginToMigrateTo; } diff --git a/public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts b/public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts index 5666fa3710d..dfb980579ab 100644 --- a/public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts +++ b/public/app/features/dashboard/state/DashboardMigratorToBackend.test.ts @@ -94,7 +94,6 @@ describe('Backend / Frontend result comparison', () => { for (const nestedPanel of panel.panels) { const panelPluginToMigrateTo = getPanelPluginToMigrateTo(nestedPanel); if (panelPluginToMigrateTo) { - // @ts-expect-error - we are using the type from the frontend migration result nestedPanel.autoMigrateFrom = nestedPanel.type; nestedPanel.type = panelPluginToMigrateTo; } From e21bdbe89a7500e3d1ce15d368495f3fb3c91901 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 14:47:05 -0700 Subject: [PATCH 170/297] Release: update changelog for main (#115424) * Update changelog * restart ci * Update changelog * restart ci * Update changelog * restart ci * Update changelog * Update changelog * restart ci * restart ci --------- Co-authored-by: github-actions[bot] Co-authored-by: jev forsberg --- CHANGELOG.md | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4dfcb3171a..6bacfafdfd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,101 @@ + + +# 12.3.1 (2025-12-16) + +### Features and enhancements + +- **Alerting:** Update alerting dependency [#114259](https://github.com/grafana/grafana/pull/114259), [@moustafab](https://github.com/moustafab) +- **Azure:** Improved column handling in logs query builder [#114841](https://github.com/grafana/grafana/pull/114841), [@aangelisc](https://github.com/aangelisc) +- **Azure:** Include aggregate columns in logs builder [#114835](https://github.com/grafana/grafana/pull/114835), [@aangelisc](https://github.com/aangelisc) +- **Dependencies:** Bump Go to v1.25.5 [#114751](https://github.com/grafana/grafana/pull/114751), [@macabu](https://github.com/macabu) +- **Docs:** Clarify section title for repeating rows and tabs [#115346](https://github.com/grafana/grafana/pull/115346), [@imatwawana](https://github.com/imatwawana) +- **Plugins:** Add PluginContext to plugins when scenes is disabled [#115064](https://github.com/grafana/grafana/pull/115064), [@hugohaggmark](https://github.com/hugohaggmark) +- **QueryEditorRows:** Clear hideSeriesFrom override on query edit [#114628](https://github.com/grafana/grafana/pull/114628), [@Sergej-Vlasov](https://github.com/Sergej-Vlasov) + +### Bug fixes + +- **Azure:** Fix `dcount` aggregation [#114907](https://github.com/grafana/grafana/pull/114907), [@aangelisc](https://github.com/aangelisc) +- **Azure:** Fix `percentile` syntax [#114707](https://github.com/grafana/grafana/pull/114707), [@aangelisc](https://github.com/aangelisc) +- **Dashboards:** Fix empty space under time controls when a dashboard has a lot of variables [#114730](https://github.com/grafana/grafana/pull/114730), [@oscarkilhed](https://github.com/oscarkilhed) +- **Plugins:** Datasource breadcrumb link should link to settings tab [#113910](https://github.com/grafana/grafana/pull/113910), [@wbrowne](https://github.com/wbrowne) +- **Postgresql:** Fix variable interpolation logic when the variable has multiple values [#114876](https://github.com/grafana/grafana/pull/114876), [@itsmylife](https://github.com/itsmylife) + + + + +# 12.2.3 (2025-12-16) + +### Features and enhancements + +- **Alerting:** Update alerting dependency [#114256](https://github.com/grafana/grafana/pull/114256), [@moustafab](https://github.com/moustafab) +- **Azure:** Improved column handling in logs query builder [#114840](https://github.com/grafana/grafana/pull/114840), [@aangelisc](https://github.com/aangelisc) +- **Azure:** Include aggregate columns in logs builder [#114834](https://github.com/grafana/grafana/pull/114834), [@aangelisc](https://github.com/aangelisc) +- **Dependencies:** Bump Go to v1.25.5 [#114753](https://github.com/grafana/grafana/pull/114753), [@macabu](https://github.com/macabu) +- **Plugins:** Add PluginContext to plugins when scenes is disabled [#115063](https://github.com/grafana/grafana/pull/115063), [@hugohaggmark](https://github.com/hugohaggmark) +- **QueryEditorRows:** Clear hideSeriesFrom override on query edit [#114629](https://github.com/grafana/grafana/pull/114629), [@Sergej-Vlasov](https://github.com/Sergej-Vlasov) + +### Bug fixes + +- **Alerting:** Fix contact points issue [#115412](https://github.com/grafana/grafana/pull/115412), [@yuri-tceretian](https://github.com/yuri-tceretian) +- **Azure:** Fix `dcount` aggregation [#114906](https://github.com/grafana/grafana/pull/114906), [@aangelisc](https://github.com/aangelisc) +- **Azure:** Fix `percentile` syntax [#114706](https://github.com/grafana/grafana/pull/114706), [@aangelisc](https://github.com/aangelisc) +- **Postgresql:** Fix variable interpolation logic when the variable has multiple values [#114875](https://github.com/grafana/grafana/pull/114875), [@itsmylife](https://github.com/itsmylife) + + + + +# 12.1.5 (2025-12-16) + +### Features and enhancements + +- **Alerting:** Update alerting dependency [#114254](https://github.com/grafana/grafana/pull/114254), [@moustafab](https://github.com/moustafab) +- **Dependencies:** Bump Go to v1.25.5 [#114755](https://github.com/grafana/grafana/pull/114755), [@macabu](https://github.com/macabu) +- **Docs:** Clarify section title for repeating rows and tabs [#115344](https://github.com/grafana/grafana/pull/115344), [@imatwawana](https://github.com/imatwawana) +- **Plugins:** Add PluginContext to plugins when scenes is disabled [#115062](https://github.com/grafana/grafana/pull/115062), [@hugohaggmark](https://github.com/hugohaggmark) + +### Bug fixes + +- **Alerting:** Fix contact points issue [#115411](https://github.com/grafana/grafana/pull/115411), [@yuri-tceretian](https://github.com/yuri-tceretian) +- **Azure:** Fix `dcount` aggregation [#114905](https://github.com/grafana/grafana/pull/114905), [@aangelisc](https://github.com/aangelisc) +- **Azure:** Fix `percentile` syntax [#114705](https://github.com/grafana/grafana/pull/114705), [@aangelisc](https://github.com/aangelisc) +- **Postgresql:** Fix variable interpolation logic when the variable has multiple values [#114874](https://github.com/grafana/grafana/pull/114874), [@itsmylife](https://github.com/itsmylife) + + + + +# 12.0.8 (2025-12-16) + +### Features and enhancements + +- **Alerting:** Update alerting dependency [#114252](https://github.com/grafana/grafana/pull/114252), [@moustafab](https://github.com/moustafab) +- **Dependencies:** Bump Go to v1.25.5 [#114756](https://github.com/grafana/grafana/pull/114756), [@macabu](https://github.com/macabu) +- **Docs:** Clarify section title for repeating rows and tabs [#115343](https://github.com/grafana/grafana/pull/115343), [@imatwawana](https://github.com/imatwawana) +- **Plugins:** Add PluginContext to plugins when scenes is disabled [#115061](https://github.com/grafana/grafana/pull/115061), [@hugohaggmark](https://github.com/hugohaggmark) + +### Bug fixes + +- **Alerting:** Fix contact points issue [#115410](https://github.com/grafana/grafana/pull/115410), [@yuri-tceretian](https://github.com/yuri-tceretian) +- **Azure:** Fix `dcount` aggregation [#114904](https://github.com/grafana/grafana/pull/114904), [@aangelisc](https://github.com/aangelisc) +- **Azure:** Fix `percentile` syntax [#114704](https://github.com/grafana/grafana/pull/114704), [@aangelisc](https://github.com/aangelisc) +- **Postgresql:** Fix variable interpolation logic when the variable has multiple values [#114873](https://github.com/grafana/grafana/pull/114873), [@itsmylife](https://github.com/itsmylife) + + + + +# 11.6.9 (2025-12-16) + +### Features and enhancements + +- **Alerting:** Update alerting dependency [#114249](https://github.com/grafana/grafana/pull/114249), [@moustafab](https://github.com/moustafab) +- **Dependencies:** Bump Go to v1.25.5 [#114757](https://github.com/grafana/grafana/pull/114757), [@macabu](https://github.com/macabu) +- **PDFTables:** Dynamically shrink font to try and fit whole table in pdf page width (Enterprise) +- **Plugins:** Add PluginContext to plugins when scenes is disabled [#115060](https://github.com/grafana/grafana/pull/115060), [@hugohaggmark](https://github.com/hugohaggmark) + +### Bug fixes + +- **Alerting:** Fix contacts point issues [#115409](https://github.com/grafana/grafana/pull/115409), [@yuri-tceretian](https://github.com/yuri-tceretian) + + # 12.3.0 (2025-11-19) From 706468f2b7a75cc3b95c1527585c174293e63c78 Mon Sep 17 00:00:00 2001 From: Jacob Valdez Date: Tue, 16 Dec 2025 16:10:49 -0600 Subject: [PATCH 171/297] docs: update the technical note for annotation bloat (#115267) --- .../upgrade-guide/upgrade-v12.0/index.md | 56 +++++++++++++++---- .../upgrade-guide/upgrade-v12.1/index.md | 56 +++++++++++++++---- .../upgrade-guide/upgrade-v12.2/index.md | 56 +++++++++++++++---- .../upgrade-guide/upgrade-v12.3/index.md | 56 +++++++++++++++---- 4 files changed, 180 insertions(+), 44 deletions(-) diff --git a/docs/sources/upgrade-guide/upgrade-v12.0/index.md b/docs/sources/upgrade-guide/upgrade-v12.0/index.md index feb1061dceb..b166cc2c022 100644 --- a/docs/sources/upgrade-guide/upgrade-v12.0/index.md +++ b/docs/sources/upgrade-guide/upgrade-v12.0/index.md @@ -81,24 +81,26 @@ Since Grafana 10.2, the endpoint to check compatible versions when installing a We _do not_ recommend installing plugins declared as incompatible. However, if you need to force install a plugin despite it being declared as incompatible, refer to the [Installing a plugin from a ZIP](https://grafana.com/docs/grafana/latest/administration/plugin-management/#install-a-plugin-from-a-zip-file) guidance. -### PostgreSQL annotation table migration +### Annotation table migration **Plan for increased disk usage when upgrading from Grafana v11.x** -Upgrading from Grafana v11.x to Grafana v12.x triggers a full-table rewrite of the PostgreSQL `annotation` table. The migration populates the new `dashboard_uid` column, which causes PostgreSQL to rewrite the entire table and rebuild its indexes. +Upgrading from Grafana v11.x to Grafana v12.x triggers a full-table rewrite of the `annotation` table. The migration populates the new `dashboard_uid` column, which causes the database to rewrite the entire table and rebuild its indexes. Environments with large annotation datasets can experience significant temporary disk usage increase, which may lead to: -- Rapid disk consumption on the PostgreSQL data volume +- Rapid disk consumption on the database data volume - Database migration failures (for example, "could not extend file: No space left on device") - Grafana startup failures - Extended downtime during the upgrade process #### How do I know if I'm affected? -You're affected if you're upgrading from Grafana v11.x to v12.x and you have a large `annotation` table in your PostgreSQL database. +You're affected if you're upgrading from Grafana v11.x to v12.x and you have a large `annotation` table in your database. -To check your annotation table size, connect to your PostgreSQL database and run the following query: +To check your annotation table size, connect to your database and check the table size. + +For PostgreSQL, run the following query: ```sql SELECT @@ -107,26 +109,58 @@ SELECT pg_size_pretty(pg_total_relation_size('annotation')) AS total_size; ``` +For MySQL, run the following query: + +```sql +SELECT + ROUND(data_length / 1024 / 1024, 2) AS table_size_mb, + ROUND(index_length / 1024 / 1024, 2) AS indexes_size_mb, + ROUND((data_length + index_length) / 1024 / 1024, 2) AS total_size_mb +FROM information_schema.tables +WHERE table_schema = DATABASE() + AND table_name = 'annotation'; +``` + +For SQLite, check the database file size directly, as SQLite stores all tables in a single file. You can run the following command from your terminal: + +```bash +ls -lh +``` + If your total size is several gigabytes or more, you should plan accordingly before upgrading. #### What should I do before upgrading? Before you upgrade, take the following steps: -1. **Verify available disk space**: Ensure you have at least 2-3 times the current `annotation` table size available as free disk space on your PostgreSQL data volume. +1. **Verify available disk space**: Ensure you have at least 2-3 times the current `annotation` table size available as free disk space on your database data volume. -2. **Review your annotation data**: Consider whether you need to retain all historical annotations. +1. **Review your annotation data**: Consider whether you need to retain all historical annotations. -3. **Clean up old annotations (optional)**: If you have annotations you don't need, remove them before upgrading. +1. **Clean up old annotations (optional)**: If you have annotations you don't need, remove them before upgrading. -4. **Back up your database**: Always back up your Grafana database before performing an upgrade. For more information, refer to [Back up Grafana](#back-up-grafana). +1. **Back up your database**: Always back up your Grafana database before performing an upgrade. For more information, refer to [Back up Grafana](#back-up-grafana). #### What should I do after upgrading? -After successfully upgrading to Grafana v12.x, you can reclaim disk space by running a `VACUUM FULL` operation on the `annotation` table during a maintenance window: +After successfully upgrading to Grafana v12.x, you can reclaim disk space by performing database maintenance operations during a maintenance window. + +For PostgreSQL, run a `VACUUM FULL` operation on the `annotation` table: ```sql VACUUM FULL annotation; ``` -This operation requires a lock on the table and may take significant time depending on the table size. Plan to run this during a low-traffic period. +For MySQL, run an `OPTIMIZE TABLE` operation on the `annotation` table: + +```sql +OPTIMIZE TABLE annotation; +``` + +For SQLite, run a `VACUUM` operation on the database: + +```sql +VACUUM; +``` + +These operations require a lock on the table and may take significant time depending on the table size. Plan to run these during a low-traffic period. diff --git a/docs/sources/upgrade-guide/upgrade-v12.1/index.md b/docs/sources/upgrade-guide/upgrade-v12.1/index.md index 7dbcb11ece9..9a96c0cbc0a 100644 --- a/docs/sources/upgrade-guide/upgrade-v12.1/index.md +++ b/docs/sources/upgrade-guide/upgrade-v12.1/index.md @@ -21,24 +21,26 @@ weight: 499 ## Technical notes -### PostgreSQL annotation table migration +### Annotation table migration **Plan for increased disk usage when upgrading from Grafana v11.x** -Upgrading from Grafana v11.x to Grafana v12.x triggers a full-table rewrite of the PostgreSQL `annotation` table. The migration populates the new `dashboard_uid` column, which causes PostgreSQL to rewrite the entire table and rebuild its indexes. +Upgrading from Grafana v11.x to Grafana v12.x triggers a full-table rewrite of the `annotation` table. The migration populates the new `dashboard_uid` column, which causes the database to rewrite the entire table and rebuild its indexes. Environments with large annotation datasets can experience significant temporary disk usage increase, which may lead to: -- Rapid disk consumption on the PostgreSQL data volume +- Rapid disk consumption on the database data volume - Database migration failures (for example, "could not extend file: No space left on device") - Grafana startup failures - Extended downtime during the upgrade process #### How do I know if I'm affected? -You're affected if you're upgrading from Grafana v11.x to v12.x and you have a large `annotation` table in your PostgreSQL database. +You're affected if you're upgrading from Grafana v11.x to v12.x and you have a large `annotation` table in your database. -To check your annotation table size, connect to your PostgreSQL database and run the following query: +To check your annotation table size, connect to your database and check the table size. + +For PostgreSQL, run the following query: ```sql SELECT @@ -47,26 +49,58 @@ SELECT pg_size_pretty(pg_total_relation_size('annotation')) AS total_size; ``` +For MySQL, run the following query: + +```sql +SELECT + ROUND(data_length / 1024 / 1024, 2) AS table_size_mb, + ROUND(index_length / 1024 / 1024, 2) AS indexes_size_mb, + ROUND((data_length + index_length) / 1024 / 1024, 2) AS total_size_mb +FROM information_schema.tables +WHERE table_schema = DATABASE() + AND table_name = 'annotation'; +``` + +For SQLite, check the database file size directly, as SQLite stores all tables in a single file. You can run the following command from your terminal: + +```bash +ls -lh +``` + If your total size is several gigabytes or more, you should plan accordingly before upgrading. #### What should I do before upgrading? Before you upgrade, take the following steps: -1. **Verify available disk space**: Ensure you have at least 2-3 times the current `annotation` table size available as free disk space on your PostgreSQL data volume. +1. **Verify available disk space**: Ensure you have at least 2-3 times the current `annotation` table size available as free disk space on your database data volume. -2. **Review your annotation data**: Consider whether you need to retain all historical annotations. +1. **Review your annotation data**: Consider whether you need to retain all historical annotations. -3. **Clean up old annotations (optional)**: If you have annotations you don't need, remove them before upgrading. +1. **Clean up old annotations (optional)**: If you have annotations you don't need, remove them before upgrading. -4. **Back up your database**: Always back up your Grafana database before performing an upgrade. For more information, refer to [Back up Grafana](#back-up-grafana). +1. **Back up your database**: Always back up your Grafana database before performing an upgrade. For more information, refer to [Back up Grafana](#back-up-grafana). #### What should I do after upgrading? -After successfully upgrading to Grafana v12.x, you can reclaim disk space by running a `VACUUM FULL` operation on the `annotation` table during a maintenance window: +After successfully upgrading to Grafana v12.x, you can reclaim disk space by performing database maintenance operations during a maintenance window. + +For PostgreSQL, run a `VACUUM FULL` operation on the `annotation` table: ```sql VACUUM FULL annotation; ``` -This operation requires a lock on the table and may take significant time depending on the table size. Plan to run this during a low-traffic period. +For MySQL, run an `OPTIMIZE TABLE` operation on the `annotation` table: + +```sql +OPTIMIZE TABLE annotation; +``` + +For SQLite, run a `VACUUM` operation on the database: + +```sql +VACUUM; +``` + +These operations require a lock on the table and may take significant time depending on the table size. Plan to run these during a low-traffic period. diff --git a/docs/sources/upgrade-guide/upgrade-v12.2/index.md b/docs/sources/upgrade-guide/upgrade-v12.2/index.md index 1e900d65793..221ae3f152c 100644 --- a/docs/sources/upgrade-guide/upgrade-v12.2/index.md +++ b/docs/sources/upgrade-guide/upgrade-v12.2/index.md @@ -21,24 +21,26 @@ weight: 498 ## Technical notes -### PostgreSQL annotation table migration +### Annotation table migration **Plan for increased disk usage when upgrading from Grafana v11.x** -Upgrading from Grafana v11.x to Grafana v12.x triggers a full-table rewrite of the PostgreSQL `annotation` table. The migration populates the new `dashboard_uid` column, which causes PostgreSQL to rewrite the entire table and rebuild its indexes. +Upgrading from Grafana v11.x to Grafana v12.x triggers a full-table rewrite of the `annotation` table. The migration populates the new `dashboard_uid` column, which causes the database to rewrite the entire table and rebuild its indexes. Environments with large annotation datasets can experience significant temporary disk usage increase, which may lead to: -- Rapid disk consumption on the PostgreSQL data volume +- Rapid disk consumption on the database data volume - Database migration failures (for example, "could not extend file: No space left on device") - Grafana startup failures - Extended downtime during the upgrade process #### How do I know if I'm affected? -You're affected if you're upgrading from Grafana v11.x to v12.x and you have a large `annotation` table in your PostgreSQL database. +You're affected if you're upgrading from Grafana v11.x to v12.x and you have a large `annotation` table in your database. -To check your annotation table size, connect to your PostgreSQL database and run the following query: +To check your annotation table size, connect to your database and check the table size. + +For PostgreSQL, run the following query: ```sql SELECT @@ -47,26 +49,58 @@ SELECT pg_size_pretty(pg_total_relation_size('annotation')) AS total_size; ``` +For MySQL, run the following query: + +```sql +SELECT + ROUND(data_length / 1024 / 1024, 2) AS table_size_mb, + ROUND(index_length / 1024 / 1024, 2) AS indexes_size_mb, + ROUND((data_length + index_length) / 1024 / 1024, 2) AS total_size_mb +FROM information_schema.tables +WHERE table_schema = DATABASE() + AND table_name = 'annotation'; +``` + +For SQLite, check the database file size directly, as SQLite stores all tables in a single file. You can run the following command from your terminal: + +```bash +ls -lh +``` + If your total size is several gigabytes or more, you should plan accordingly before upgrading. #### What should I do before upgrading? Before you upgrade, take the following steps: -1. **Verify available disk space**: Ensure you have at least 2-3 times the current `annotation` table size available as free disk space on your PostgreSQL data volume. +1. **Verify available disk space**: Ensure you have at least 2-3 times the current `annotation` table size available as free disk space on your database data volume. -2. **Review your annotation data**: Consider whether you need to retain all historical annotations. +1. **Review your annotation data**: Consider whether you need to retain all historical annotations. -3. **Clean up old annotations (optional)**: If you have annotations you don't need, remove them before upgrading. +1. **Clean up old annotations (optional)**: If you have annotations you don't need, remove them before upgrading. -4. **Back up your database**: Always back up your Grafana database before performing an upgrade. For more information, refer to [Back up Grafana](#back-up-grafana). +1. **Back up your database**: Always back up your Grafana database before performing an upgrade. For more information, refer to [Back up Grafana](#back-up-grafana). #### What should I do after upgrading? -After successfully upgrading to Grafana v12.x, you can reclaim disk space by running a `VACUUM FULL` operation on the `annotation` table during a maintenance window: +After successfully upgrading to Grafana v12.x, you can reclaim disk space by performing database maintenance operations during a maintenance window. + +For PostgreSQL, run a `VACUUM FULL` operation on the `annotation` table: ```sql VACUUM FULL annotation; ``` -This operation requires a lock on the table and may take significant time depending on the table size. Plan to run this during a low-traffic period. +For MySQL, run an `OPTIMIZE TABLE` operation on the `annotation` table: + +```sql +OPTIMIZE TABLE annotation; +``` + +For SQLite, run a `VACUUM` operation on the database: + +```sql +VACUUM; +``` + +These operations require a lock on the table and may take significant time depending on the table size. Plan to run these during a low-traffic period. diff --git a/docs/sources/upgrade-guide/upgrade-v12.3/index.md b/docs/sources/upgrade-guide/upgrade-v12.3/index.md index dc8b1b8e398..6160c443e10 100644 --- a/docs/sources/upgrade-guide/upgrade-v12.3/index.md +++ b/docs/sources/upgrade-guide/upgrade-v12.3/index.md @@ -21,24 +21,26 @@ weight: 497 ## Technical notes -### PostgreSQL annotation table migration +### Annotation table migration **Plan for increased disk usage when upgrading from Grafana v11.x** -Upgrading from Grafana v11.x to Grafana v12.x triggers a full-table rewrite of the PostgreSQL `annotation` table. The migration populates the new `dashboard_uid` column, which causes PostgreSQL to rewrite the entire table and rebuild its indexes. +Upgrading from Grafana v11.x to Grafana v12.x triggers a full-table rewrite of the `annotation` table. The migration populates the new `dashboard_uid` column, which causes the database to rewrite the entire table and rebuild its indexes. Environments with large annotation datasets can experience significant temporary disk usage increase, which may lead to: -- Rapid disk consumption on the PostgreSQL data volume +- Rapid disk consumption on the database data volume - Database migration failures (for example, "could not extend file: No space left on device") - Grafana startup failures - Extended downtime during the upgrade process #### How do I know if I'm affected? -You're affected if you're upgrading from Grafana v11.x to v12.x and you have a large `annotation` table in your PostgreSQL database. +You're affected if you're upgrading from Grafana v11.x to v12.x and you have a large `annotation` table in your database. -To check your annotation table size, connect to your PostgreSQL database and run the following query: +To check your annotation table size, connect to your database and check the table size. + +For PostgreSQL, run the following query: ```sql SELECT @@ -47,26 +49,58 @@ SELECT pg_size_pretty(pg_total_relation_size('annotation')) AS total_size; ``` +For MySQL, run the following query: + +```sql +SELECT + ROUND(data_length / 1024 / 1024, 2) AS table_size_mb, + ROUND(index_length / 1024 / 1024, 2) AS indexes_size_mb, + ROUND((data_length + index_length) / 1024 / 1024, 2) AS total_size_mb +FROM information_schema.tables +WHERE table_schema = DATABASE() + AND table_name = 'annotation'; +``` + +For SQLite, check the database file size directly, as SQLite stores all tables in a single file. You can run the following command from your terminal: + +```bash +ls -lh +``` + If your total size is several gigabytes or more, you should plan accordingly before upgrading. #### What should I do before upgrading? Before you upgrade, take the following steps: -1. **Verify available disk space**: Ensure you have at least 2-3 times the current `annotation` table size available as free disk space on your PostgreSQL data volume. +1. **Verify available disk space**: Ensure you have at least 2-3 times the current `annotation` table size available as free disk space on your database data volume. -2. **Review your annotation data**: Consider whether you need to retain all historical annotations. +1. **Review your annotation data**: Consider whether you need to retain all historical annotations. -3. **Clean up old annotations (optional)**: If you have annotations you don't need, remove them before upgrading. +1. **Clean up old annotations (optional)**: If you have annotations you don't need, remove them before upgrading. -4. **Back up your database**: Always back up your Grafana database before performing an upgrade. For more information, refer to [Back up Grafana](#back-up-grafana). +1. **Back up your database**: Always back up your Grafana database before performing an upgrade. For more information, refer to [Back up Grafana](#back-up-grafana). #### What should I do after upgrading? -After successfully upgrading to Grafana v12.x, you can reclaim disk space by running a `VACUUM FULL` operation on the `annotation` table during a maintenance window: +After successfully upgrading to Grafana v12.x, you can reclaim disk space by performing database maintenance operations during a maintenance window. + +For PostgreSQL, run a `VACUUM FULL` operation on the `annotation` table: ```sql VACUUM FULL annotation; ``` -This operation requires a lock on the table and may take significant time depending on the table size. Plan to run this during a low-traffic period. +For MySQL, run an `OPTIMIZE TABLE` operation on the `annotation` table: + +```sql +OPTIMIZE TABLE annotation; +``` + +For SQLite, run a `VACUUM` operation on the database: + +```sql +VACUUM; +``` + +These operations require a lock on the table and may take significant time depending on the table size. Plan to run these during a low-traffic period. From 148ea7af3ce84bbe031babde3c8144a11ab8b759 Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 00:41:40 +0000 Subject: [PATCH 172/297] I18n: Download translations from Crowdin (#115462) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 16 ++++++++++++++-- public/locales/de-DE/grafana.json | 16 ++++++++++++++-- public/locales/es-ES/grafana.json | 16 ++++++++++++++-- public/locales/fr-FR/grafana.json | 16 ++++++++++++++-- public/locales/hu-HU/grafana.json | 16 ++++++++++++++-- public/locales/id-ID/grafana.json | 16 ++++++++++++++-- public/locales/it-IT/grafana.json | 16 ++++++++++++++-- public/locales/ja-JP/grafana.json | 16 ++++++++++++++-- public/locales/ko-KR/grafana.json | 16 ++++++++++++++-- public/locales/nl-NL/grafana.json | 16 ++++++++++++++-- public/locales/pl-PL/grafana.json | 16 ++++++++++++++-- public/locales/pt-BR/grafana.json | 16 ++++++++++++++-- public/locales/pt-PT/grafana.json | 16 ++++++++++++++-- public/locales/ru-RU/grafana.json | 16 ++++++++++++++-- public/locales/sv-SE/grafana.json | 16 ++++++++++++++-- public/locales/tr-TR/grafana.json | 16 ++++++++++++++-- public/locales/zh-Hans/grafana.json | 16 ++++++++++++++-- public/locales/zh-Hant/grafana.json | 16 ++++++++++++++-- 18 files changed, 252 insertions(+), 36 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index c64175c4d1f..f933e337d3c 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -2248,6 +2248,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "Otevřít externí odkaz" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "Další", "new-menu": { @@ -4859,8 +4866,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "Hodnoty oddělené čárkou" + "modal-title": "" }, "datasource-options": { "name-filter": "Filtr názvu", @@ -11505,6 +11511,12 @@ "latestReleaseDate": "Nejnovější datum vydání:", "latestVersion": "Poslední verze", "license": "Licence", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "Nahlásit problém", "reportAbuse": "", "reportAbuseTooltip": "Nahlaste problémy související se škodlivými pluginy přímo společnosti Grafana Labs.", diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 0da32e80f94..7fdd08a6a13 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -2232,6 +2232,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "Externen Link öffnen" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "Mehr", "new-menu": { @@ -4821,8 +4828,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "Werte werden durch Komma getrennt" + "modal-title": "" }, "datasource-options": { "name-filter": "Namensfilter", @@ -11413,6 +11419,12 @@ "latestReleaseDate": "Neuestes Release-Datum:", "latestVersion": "Aktuelle Version", "license": "Lizenz", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "Ein Problem melden", "reportAbuse": "", "reportAbuseTooltip": "Melden Sie Probleme im Zusammenhang mit bösartigen oder schädlichen Plugins direkt an Grafana Labs.", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index c44e8c8ff3b..5e36b5223c4 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -2232,6 +2232,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "Abrir enlace externo" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "Más", "new-menu": { @@ -4821,8 +4828,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "Valores separados por coma" + "modal-title": "" }, "datasource-options": { "name-filter": "Nombrar filtro", @@ -11413,6 +11419,12 @@ "latestReleaseDate": "Fecha último lanzamiento:", "latestVersion": "Versión más reciente", "license": "Licencia", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "Comunicar un problema", "reportAbuse": "", "reportAbuseTooltip": "Informa de problemas relacionados con complementos maliciosos o dañinos directamente a Grafana Labs.", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index aecae73acab..a6de953984c 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -2232,6 +2232,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "Ouvrir le lien externe" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "Plus", "new-menu": { @@ -4821,8 +4828,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "Valeurs séparées par une virgule" + "modal-title": "" }, "datasource-options": { "name-filter": "Nom du filtre", @@ -11413,6 +11419,12 @@ "latestReleaseDate": "Dernière date de sortie :", "latestVersion": "Dernière version", "license": "Licence", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "Signaler un problème", "reportAbuse": "", "reportAbuseTooltip": "Signalez les problèmes liés à des plugins malveillants ou nuisibles directement à Grafana Labs.", diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 7f049004022..c0250296917 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -2232,6 +2232,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "Külső hivatkozás megnyitása" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "Továbbiak", "new-menu": { @@ -4821,8 +4828,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "Értékek vesszővel elválasztva" + "modal-title": "" }, "datasource-options": { "name-filter": "Névszűrő", @@ -11413,6 +11419,12 @@ "latestReleaseDate": "Legújabb kiadás dátuma:", "latestVersion": "Legújabb verzió", "license": "Licenc", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "Probléma felvetése", "reportAbuse": "", "reportAbuseTooltip": "A rosszindulatú vagy kártékony bővítményekkel kapcsolatos problémák jelentése közvetlenül a Grafana Labs vállalatnak.", diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 1676500a4b3..12e57299535 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -2224,6 +2224,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "Buka tautan eksternal" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "Lebih banyak", "new-menu": { @@ -4802,8 +4809,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "Nilai dipisahkan dengan koma" + "modal-title": "" }, "datasource-options": { "name-filter": "Filter nama", @@ -11367,6 +11373,12 @@ "latestReleaseDate": "Tanggal rilis terbaru:", "latestVersion": "Versi Terbaru", "license": "Lisensi", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "Kemukakan masalah", "reportAbuse": "", "reportAbuseTooltip": "Laporkan masalah yang terkait dengan plugin jahat atau berbahaya langsung ke Grafana Labs.", diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index ef4e977fca8..c25f34fd338 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -2232,6 +2232,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "Apri collegamento esterno" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "Altro", "new-menu": { @@ -4821,8 +4828,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "Valori separati da virgola" + "modal-title": "" }, "datasource-options": { "name-filter": "Filtro nome", @@ -11413,6 +11419,12 @@ "latestReleaseDate": "Data dell'ultima versione:", "latestVersion": "Versione più recente", "license": "Licenza", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "Segnala un problema", "reportAbuse": "", "reportAbuseTooltip": "Segnala i problemi relativi a plug-in dannosi o pericolosi direttamente a Grafana Labs.", diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index feb94f34766..528a0b4ac59 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -2224,6 +2224,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "外部リンクを開く" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "その他", "new-menu": { @@ -4802,8 +4809,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "カンマで区切った値" + "modal-title": "" }, "datasource-options": { "name-filter": "名前フィルター", @@ -11367,6 +11373,12 @@ "latestReleaseDate": "最新リリース日:", "latestVersion": "最新のバージョン", "license": "ライセンス", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "問題を提起する", "reportAbuse": "", "reportAbuseTooltip": "悪意のあるプラグインや有害なプラグインに関連する問題は、Grafana Labsに直接報告してください。", diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 24ea525fbb6..5b5dbf90754 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -2224,6 +2224,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "외부 링크 열기" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "더 보기", "new-menu": { @@ -4802,8 +4809,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "쉼표로 구분된 값" + "modal-title": "" }, "datasource-options": { "name-filter": "이름 필터", @@ -11367,6 +11373,12 @@ "latestReleaseDate": "최신 릴리스 날짜:", "latestVersion": "최신 버전", "license": "라이선스", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "문제 제기", "reportAbuse": "", "reportAbuseTooltip": "악성 또는 유해한 플러그인과 관련된 문제는 바로 Grafana Labs에 신고해 주세요.", diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index facfe8cee8e..6040824f752 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -2232,6 +2232,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "Externe link openen" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "Meer", "new-menu": { @@ -4821,8 +4828,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "Waarden gescheiden door komma" + "modal-title": "" }, "datasource-options": { "name-filter": "Filter een naam geven", @@ -11413,6 +11419,12 @@ "latestReleaseDate": "Uiterste releasedatum:", "latestVersion": "Nieuwste versie", "license": "Licentie", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "Een probleem melden", "reportAbuse": "", "reportAbuseTooltip": "Meld problemen met kwaadaardige of schadelijke plug-ins rechtstreeks aan Grafana Labs.", diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 98cfa899c2e..91d924d3ca7 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -2248,6 +2248,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "Otwórz link zewnętrzny" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "Więcej", "new-menu": { @@ -4859,8 +4866,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "Wartości rozdzielone przecinkami" + "modal-title": "" }, "datasource-options": { "name-filter": "Filtr nazwy", @@ -11505,6 +11511,12 @@ "latestReleaseDate": "Data wydania najnowszej wersji:", "latestVersion": "Najnowsza wersja", "license": "Licencja", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "Zgłoś sprawę", "reportAbuse": "", "reportAbuseTooltip": "Zgłaszaj sprawy związane ze złośliwymi lub szkodliwymi wtyczkami bezpośrednio do Grafana Labs.", diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 542bd14c8a1..0a59679f4a8 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -2232,6 +2232,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "Abrir link externo" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "Mais", "new-menu": { @@ -4821,8 +4828,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "Valores separados por vírgula" + "modal-title": "" }, "datasource-options": { "name-filter": "Filtro de nome", @@ -11413,6 +11419,12 @@ "latestReleaseDate": "Data de lançamento mais recente:", "latestVersion": "Última versão", "license": "Licença", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "Comunicar um problema", "reportAbuse": "", "reportAbuseTooltip": "Comunique problemas relacionados a plug-ins mal-intencionados ou prejudiciais diretamente à Grafana Labs.", diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index 30c132e560d..fa614655ae0 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -2232,6 +2232,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "Abrir link externo" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "Mais", "new-menu": { @@ -4821,8 +4828,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "Valores separados por vírgulas" + "modal-title": "" }, "datasource-options": { "name-filter": "Filtro de nome", @@ -11413,6 +11419,12 @@ "latestReleaseDate": "Data de lançamento mais recente:", "latestVersion": "Versão mais recente", "license": "Licença", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "Comunicar um problema", "reportAbuse": "", "reportAbuseTooltip": "Comunique problemas relacionados com plugins maliciosos ou prejudiciais diretamente à Grafana Labs.", diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index ade7a64fe21..667bcac393c 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -2248,6 +2248,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "Открыть внешнюю ссылку" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "Еще", "new-menu": { @@ -4859,8 +4866,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "Значения, разделенные запятыми" + "modal-title": "" }, "datasource-options": { "name-filter": "Фильтр по названию", @@ -11505,6 +11511,12 @@ "latestReleaseDate": "Дата последнего выпуска:", "latestVersion": "Новейшая версия", "license": "Лицензия", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "Сообщить о проблеме", "reportAbuse": "", "reportAbuseTooltip": "Сообщайте о проблемах, связанных с вредоносными или опасными плагинами, непосредственно в Grafana Labs.", diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 45669bf75c5..e37fcfe4df4 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -2232,6 +2232,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "Öppen extern länk" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "Mer", "new-menu": { @@ -4821,8 +4828,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "Värden åtskilda med kommatecken" + "modal-title": "" }, "datasource-options": { "name-filter": "Namnfilter", @@ -11413,6 +11419,12 @@ "latestReleaseDate": "Senaste utgivningsdatum:", "latestVersion": "Senaste versionen", "license": "Licens", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "Ta upp ett problem", "reportAbuse": "", "reportAbuseTooltip": "Rapportera problem relaterade till skadliga eller farliga tilläggsprogram direkt till Grafana Labs.", diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 2277af7feb5..d2cbb01804d 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -2232,6 +2232,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "Daha Fazla", "new-menu": { @@ -4821,8 +4828,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "Virgülle ayrılmış değerler" + "modal-title": "" }, "datasource-options": { "name-filter": "Ad filtresi", @@ -11413,6 +11419,12 @@ "latestReleaseDate": "Son yayın tarihi:", "latestVersion": "En Son Sürüm", "license": "Lisans", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "Sorun bildir", "reportAbuse": "", "reportAbuseTooltip": "Zararlı veya kötü amaçlı eklentilere ilişkin sorunları doğrudan Grafana Labs'a bildirin.", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index c6a34de778b..1b08b386268 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -2224,6 +2224,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "打开外部链接" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "更多", "new-menu": { @@ -4802,8 +4809,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "以逗号分隔的值" + "modal-title": "" }, "datasource-options": { "name-filter": "名称筛选器", @@ -11367,6 +11373,12 @@ "latestReleaseDate": "最新发布日期:", "latestVersion": "最新版本", "license": "许可", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "报告问题", "reportAbuse": "", "reportAbuseTooltip": "直接向 Grafana Labs 报告与恶意或有害插件相关的问题。", diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index 4a569b10b0f..ba9708811e3 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -2224,6 +2224,13 @@ "receiver-metadata-badge": { "aria-label-open-external-link": "開啟外部連結" }, + "receivers": { + "protected": { + "field": { + "description": "" + } + } + }, "receivers-section": { "button-more": "更多", "new-menu": { @@ -4802,8 +4809,7 @@ "apply": "", "change-value": "", "discard": "", - "modal-title": "", - "values": "以逗號分隔的值" + "modal-title": "" }, "datasource-options": { "name-filter": "名稱篩選", @@ -11367,6 +11373,12 @@ "latestReleaseDate": "最新發佈日期:", "latestVersion": "最新版本", "license": "授權", + "moreDetails": "", + "pluginInsights": { + "header": "" + }, + "pluginInsightsSuccessTooltip": "", + "pluginInsightsWarningTooltip": "", "raiseAnIssue": "提出問題", "reportAbuse": "", "reportAbuseTooltip": "將與惡意或有害外掛程式相關的問題直接報告給 Grafana Labs。", From 7e7c25af8a5a096b96e68e9873a5a1c3dca7a436 Mon Sep 17 00:00:00 2001 From: Rafael Bortolon Paulovic Date: Wed, 17 Dec 2025 06:47:21 +0100 Subject: [PATCH 173/297] Apps: update grafana-app-sdk to v0.48.7 (#115362) --- apps/advisor/go.mod | 124 ++++++----- apps/advisor/go.sum | 260 +++++++++++----------- apps/alerting/alertenrichment/go.mod | 40 ++-- apps/alerting/alertenrichment/go.sum | 88 +++++--- apps/alerting/historian/go.mod | 90 ++++---- apps/alerting/historian/go.sum | 191 +++++++++------- apps/alerting/notifications/go.mod | 101 +++++---- apps/alerting/notifications/go.sum | 221 +++++++++--------- apps/alerting/rules/go.mod | 80 ++++--- apps/alerting/rules/go.sum | 180 ++++++++------- apps/annotation/go.mod | 80 ++++--- apps/annotation/go.sum | 180 ++++++++------- apps/collections/go.mod | 58 +++-- apps/collections/go.sum | 132 ++++++----- apps/correlations/go.mod | 80 ++++--- apps/correlations/go.sum | 180 ++++++++------- apps/dashboard/go.mod | 88 ++++---- apps/dashboard/go.sum | 214 ++++++++++-------- apps/example/go.mod | 82 ++++--- apps/example/go.sum | 182 ++++++++------- apps/folder/go.mod | 58 +++-- apps/folder/go.sum | 132 ++++++----- apps/iam/go.mod | 126 +++++------ apps/iam/go.sum | 272 ++++++++++++----------- apps/investigations/go.mod | 80 ++++--- apps/investigations/go.sum | 180 ++++++++------- apps/logsdrilldown/go.mod | 80 ++++--- apps/logsdrilldown/go.sum | 180 ++++++++------- apps/playlist/go.mod | 80 ++++--- apps/playlist/go.sum | 180 ++++++++------- apps/plugins/go.mod | 116 +++++----- apps/plugins/go.sum | 243 +++++++++++--------- apps/preferences/go.mod | 58 +++-- apps/preferences/go.sum | 132 ++++++----- apps/provisioning/go.mod | 74 +++--- apps/provisioning/go.sum | 162 ++++++++------ apps/quotas/go.mod | 74 +++--- apps/quotas/go.sum | 41 ++++ apps/scope/go.mod | 38 ++-- apps/scope/go.sum | 84 ++++--- apps/sdk.mk | 2 +- apps/secret/go.mod | 61 ++--- apps/secret/go.sum | 148 ++++++------ apps/shorturl/go.mod | 82 ++++--- apps/shorturl/go.sum | 182 ++++++++------- go.mod | 132 +++++------ go.sum | 271 +++++++++++----------- go.work.sum | 16 ++ pkg/aggregator/go.mod | 103 +++++---- pkg/aggregator/go.sum | 217 +++++++++--------- pkg/apimachinery/go.mod | 52 +++-- pkg/apimachinery/go.sum | 116 ++++++---- pkg/apiserver/go.mod | 94 ++++---- pkg/apiserver/go.sum | 199 +++++++++-------- pkg/build/go.mod | 31 +-- pkg/build/go.sum | 62 +++--- pkg/build/wire/go.mod | 6 +- pkg/build/wire/go.sum | 12 +- pkg/codegen/go.mod | 24 +- pkg/codegen/go.sum | 45 ++-- pkg/plugins/codegen/go.mod | 23 +- pkg/plugins/codegen/go.sum | 52 +++-- pkg/promlib/go.mod | 76 ++++--- pkg/promlib/go.sum | 184 +++++++-------- pkg/semconv/go.mod | 3 +- pkg/semconv/go.sum | 6 +- pkg/tests/apis/config_test.go | 2 +- pkg/tests/apis/plugins/discovery_test.go | 2 +- 68 files changed, 4056 insertions(+), 3188 deletions(-) diff --git a/apps/advisor/go.mod b/apps/advisor/go.mod index 1dc4d93b4f5..ca5a4702d66 100644 --- a/apps/advisor/go.mod +++ b/apps/advisor/go.mod @@ -8,15 +8,15 @@ require ( github.com/google/go-github/v70 v70.0.0 github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 github.com/grafana/grafana v0.0.0-00010101000000-000000000000 - github.com/grafana/grafana-app-sdk v0.48.5 - github.com/grafana/grafana-app-sdk/logging v0.48.3 + github.com/grafana/grafana-app-sdk v0.48.7 + github.com/grafana/grafana-app-sdk/logging v0.48.7 github.com/grafana/grafana-plugin-sdk-go v0.284.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0 github.com/stretchr/testify v1.11.1 - k8s.io/apimachinery v0.34.2 - k8s.io/apiserver v0.34.2 - k8s.io/client-go v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + k8s.io/apimachinery v0.34.3 + k8s.io/apiserver v0.34.3 + k8s.io/client-go v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) // transitive dependencies that need replaced @@ -44,7 +44,7 @@ replace github.com/grafana/grafana/apps/plugins => ../plugins replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604 require ( - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.1 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect dario.cat/mergo v1.0.2 // indirect filippo.io/edwards25519 v1.1.0 // indirect @@ -81,7 +81,7 @@ require ( github.com/blang/semver v3.5.1+incompatible // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/bluele/gcache v0.0.2 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/bwmarrin/snowflake v0.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect @@ -89,7 +89,7 @@ require ( github.com/cheekybits/genny v1.0.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/coreos/go-semver v0.3.1 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/coreos/go-systemd/v22 v22.6.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/diegoholiveira/jsonlogic/v3 v3.7.4 // indirect @@ -118,22 +118,24 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.24.0 // indirect github.com/go-openapi/errors v0.22.3 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect github.com/go-openapi/loads v0.23.1 // indirect github.com/go-openapi/runtime v0.28.0 // indirect github.com/go-openapi/spec v0.22.0 // indirect github.com/go-openapi/strfmt v0.24.0 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/conv v0.25.1 // indirect - github.com/go-openapi/swag/fileutils v0.25.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect - github.com/go-openapi/swag/jsonutils v0.25.1 // indirect - github.com/go-openapi/swag/loading v0.25.1 // indirect - github.com/go-openapi/swag/mangling v0.25.1 // indirect - github.com/go-openapi/swag/stringutils v0.25.1 // indirect - github.com/go-openapi/swag/typeutils v0.25.1 // indirect - github.com/go-openapi/swag/yamlutils v0.25.1 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-openapi/validate v0.25.0 // indirect github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/go-stack/stack v1.8.1 // indirect @@ -151,7 +153,7 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/cel-go v0.26.1 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/google/wire v0.7.0 // indirect @@ -198,7 +200,7 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/lestrrat-go/strftime v1.0.4 // indirect github.com/lib/pq v1.10.9 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/mattetti/filebuffer v1.0.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -241,10 +243,10 @@ require ( github.com/prometheus/alertmanager v0.28.2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect + github.com/prometheus/common v0.67.4 // indirect github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/exporter-toolkit v0.14.0 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/redis/go-redis/v9 v9.14.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect @@ -256,34 +258,34 @@ require ( github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stoewer/go-strcase v1.3.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/tetratelabs/wazero v1.8.2 // indirect github.com/thomaspoignant/go-feature-flag v1.42.0 // indirect github.com/tjhop/slog-gokit v0.1.5 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect - go.etcd.io/etcd/api/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/v3 v3.6.4 // indirect + go.etcd.io/etcd/api/v3 v3.6.6 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.6 // indirect + go.etcd.io/etcd/client/v3 v3.6.6 // indirect go.mongodb.org/mongo-driver v1.17.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // indirect go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/mock v0.6.0 // indirect @@ -291,28 +293,28 @@ require ( go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools v0.40.0 // indirect golang.org/x/tools/godoc v0.1.0-deprecated // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect gonum.org/v1/gonum v0.16.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/mail.v2 v2.3.1 // indirect @@ -321,18 +323,18 @@ require ( gopkg.in/telebot.v3 v3.3.8 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/component-base v0.34.2 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/component-base v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kms v0.34.2 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect + k8s.io/kms v0.34.3 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect modernc.org/libc v1.66.10 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect modernc.org/sqlite v1.40.1 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/advisor/go.sum b/apps/advisor/go.sum index 4c5843a2ee2..760641a8857 100644 --- a/apps/advisor/go.sum +++ b/apps/advisor/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= @@ -228,8 +228,8 @@ github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2y github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw= github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -280,8 +280,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= @@ -413,10 +413,10 @@ github.com/go-openapi/analysis v0.24.0 h1:vE/VFFkICKyYuTWYnplQ+aVr45vlG6NcZKC7Bd github.com/go-openapi/analysis v0.24.0/go.mod h1:GLyoJA+bvmGGaHgpfeDh8ldpGo69fAJg7eeMDMRCIrw= github.com/go-openapi/errors v0.22.3 h1:k6Hxa5Jg1TUyZnOwV2Lh81j8ayNw5VVYLvKrp4zFKFs= github.com/go-openapi/errors v0.22.3/go.mod h1:+WvbaBBULWCOna//9B9TbLNGSFOfF8lY9dw4hGiEiKQ= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= github.com/go-openapi/loads v0.23.1 h1:H8A0dX2KDHxDzc797h0+uiCZ5kwE2+VojaQVaTlXvS0= github.com/go-openapi/loads v0.23.1/go.mod h1:hZSXkyACCWzWPQqizAv/Ye0yhi2zzHwMmoXQ6YQml44= github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= @@ -425,28 +425,36 @@ github.com/go-openapi/spec v0.22.0 h1:xT/EsX4frL3U09QviRIZXvkh80yibxQmtoEvyqug0T github.com/go-openapi/spec v0.22.0/go.mod h1:K0FhKxkez8YNS94XzF8YKEMULbFrRw4m15i2YUht4L0= github.com/go-openapi/strfmt v0.24.0 h1:dDsopqbI3wrrlIzeXRbqMihRNnjzGC+ez4NQaAAJLuc= github.com/go-openapi/strfmt v0.24.0/go.mod h1:Lnn1Bk9rZjXxU9VMADbEEOo7D7CDyKGLsSKekhFr7s4= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0= -github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs= -github.com/go-openapi/swag/fileutils v0.25.1 h1:rSRXapjQequt7kqalKXdcpIegIShhTPXx7yw0kek2uU= -github.com/go-openapi/swag/fileutils v0.25.1/go.mod h1:+NXtt5xNZZqmpIpjqcujqojGFek9/w55b3ecmOdtg8M= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= -github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8= -github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg= -github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw= -github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc= -github.com/go-openapi/swag/mangling v0.25.1 h1:XzILnLzhZPZNtmxKaz/2xIGPQsBsvmCjrJOWGNz/ync= -github.com/go-openapi/swag/mangling v0.25.1/go.mod h1:CdiMQ6pnfAgyQGSOIYnZkXvqhnnwOn997uXZMAd/7mQ= -github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw= -github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg= -github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA= -github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8= -github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk= -github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-openapi/validate v0.25.0 h1:JD9eGX81hDTjoY3WOzh6WqxVBVl7xjsLnvDo1GL5WPU= github.com/go-openapi/validate v0.25.0/go.mod h1:SUY7vKrN5FiwK6LyvSwKjDfLNirSfWwHNgxd2l29Mmw= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -537,8 +545,8 @@ github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -623,10 +631,10 @@ github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6k github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grafana/grafana-aws-sdk v1.3.0 h1:/bfJzP93rCel1GbWoRSq0oUo424MZXt8jAp2BK9w8tM= github.com/grafana/grafana-aws-sdk v1.3.0/go.mod h1:VGycF0JkCGKND2O5je1ucOqPJ0ZNhZYzV3c2bNBAaGk= github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 h1:FFcEA01tW+SmuJIuDbHOdgUBL+d7DPrZ2N4zwzPhfGk= @@ -818,8 +826,8 @@ github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/madflojo/testcerts v1.4.0 h1:I09gN0C1ly9IgeVNcAqKk8RAKIJTe3QnFrrPBDyvzN4= github.com/madflojo/testcerts v1.4.0/go.mod h1:MW8sh39gLnkKh4K0Nc55AyHEDl9l/FBLDUsQhpmkuo0= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 h1:hQWBtNqRYrI7CWIaUSXXtNKR90KzcUA5uiuxFVWw7sU= github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= @@ -1012,8 +1020,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg= @@ -1025,8 +1033,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/prometheus/prometheus v0.303.1 h1:He/2jRE6sB23Ew38AIoR1WRR3fCMgPlJA2E0obD2WSY= github.com/prometheus/prometheus v0.303.1/go.mod h1:WEq2ogBPZoLjj9x5K67VEk7ECR0nRD9XCjaOt1lsYck= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= @@ -1080,8 +1088,8 @@ github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8 github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -1139,8 +1147,8 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ github.com/wk8/go-ordered-map v1.0.0 h1:BV7z+2PaK8LTSd/mWgY12HyMAo5CEgkHqbkVq2thqr8= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= @@ -1161,15 +1169,15 @@ gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2 go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= -go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= -go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= +go.etcd.io/etcd/api/v3 v3.6.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ= +go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4= go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= -go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= +go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs= +go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI= go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= -go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= -go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= +go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec= +go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8= go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA= go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE= go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU= @@ -1195,19 +1203,19 @@ go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 h1:XfzKtKSrbtYk9TNCF8dkO0Y9M7IOfb4idCwBOTwGBiI= go.opentelemetry.io/contrib/exporters/autoexport v0.61.0/go.mod h1:N6otC+qXTD5bAnbK2O1f/1SXq3cX+3KYSWrkBUqG0cw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 h1:2pn7OzMewmYRiNtv1doZnLo3gONcnMHlFnmOR8Vgt+8= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0/go.mod h1:rjbQTDEPQymPE0YnRQp9/NuPwwtL0sesz/fnqRW/v84= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo= go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 h1:oPW/SRFyHgIgxrvNhSBzqvZER2N5kRlci3/rGTOuyWo= go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0/go.mod h1:B9Oka5QVD0bnmZNO6gBbBta6nohD/1Z+f9waH2oXyBs= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= @@ -1218,12 +1226,12 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo= go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 h1:12vMqzLLNZtXuXbJhSENRg+Vvx+ynNilV8twBLBsXMY= @@ -1235,18 +1243,18 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7i go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= @@ -1282,8 +1290,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1294,8 +1302,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A= -golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1321,8 +1329,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1377,8 +1385,8 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1401,8 +1409,8 @@ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1415,8 +1423,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1505,14 +1513,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc h1:bH6xUXay0AIFMElXG2rQ4uiE+7ncwtiOdPfYK1NK2XA= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1522,8 +1530,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1587,8 +1595,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk= golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1735,10 +1743,10 @@ google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 h1:Nt6z9UHqSlIdIGJdz6KhTIs2VRx/iOsA5iE8bmQNcxs= google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79/go.mod h1:kTmlBHMPqR5uCZPBvwa2B18mvubkjyY3CRLI0c6fj0s= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1788,8 +1796,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= @@ -1799,8 +1807,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -1837,26 +1845,26 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= -k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= -k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kms v0.34.2 h1:91rj4MDZLyIT9KxG8J5/CcMH666Z88CF/xJQeuPfJc8= -k8s.io/kms v0.34.2/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kms v0.34.3 h1:QzBOD0sk1bGQVMcZQAHGjtbP1iKZJUyhC6D0I+BTxIE= +k8s.io/kms v0.34.3/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4= modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A= @@ -1886,10 +1894,10 @@ modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/alerting/alertenrichment/go.mod b/apps/alerting/alertenrichment/go.mod index bb020e52bff..792ad18eeac 100644 --- a/apps/alerting/alertenrichment/go.mod +++ b/apps/alerting/alertenrichment/go.mod @@ -3,38 +3,48 @@ module github.com/grafana/grafana/apps/alerting/alertenrichment go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.5 + github.com/grafana/grafana-app-sdk v0.48.7 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250901080157-a0280d701b28 - k8s.io/apimachinery v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + k8s.io/apimachinery v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect - github.com/josharian/intern v1.0.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/text v0.31.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/text v0.32.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect ) diff --git a/apps/alerting/alertenrichment/go.sum b/apps/alerting/alertenrichment/go.sum index ef36568611c..9b83516bea0 100644 --- a/apps/alerting/alertenrichment/go.sum +++ b/apps/alerting/alertenrichment/go.sum @@ -1,3 +1,4 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -8,46 +9,73 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250901080157-a0280d701b28 h1:PgMfX4OPENz/iXmtDDIW9+poZY4UD0hhmXm7flVclDo= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250901080157-a0280d701b28/go.mod h1:av5N0Naq+8VV9MLF7zAkihy/mVq5UbS2EvRSJukDHlY= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= @@ -73,8 +101,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -83,8 +111,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -93,8 +121,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -102,16 +130,16 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/alerting/historian/go.mod b/apps/alerting/historian/go.mod index dc8b8ef80a9..0524e1a3852 100644 --- a/apps/alerting/historian/go.mod +++ b/apps/alerting/historian/go.mod @@ -6,15 +6,15 @@ require ( github.com/go-kit/log v0.2.1 github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 - github.com/grafana/grafana-app-sdk v0.48.5 - github.com/grafana/grafana-app-sdk/logging v0.48.3 + github.com/grafana/grafana-app-sdk v0.48.7 + github.com/grafana/grafana-app-sdk/logging v0.48.7 github.com/prometheus/client_golang v1.23.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 - go.opentelemetry.io/otel v1.38.0 - go.opentelemetry.io/otel/trace v1.38.0 - k8s.io/apimachinery v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + go.opentelemetry.io/otel v1.39.0 + go.opentelemetry.io/otel/trace v1.39.0 + k8s.io/apimachinery v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( @@ -28,11 +28,11 @@ require ( github.com/aws/aws-sdk-go v1.55.7 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/coreos/go-systemd/v22 v22.6.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect @@ -42,11 +42,21 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/errors v0.22.3 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect github.com/go-openapi/strfmt v0.24.0 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect @@ -57,7 +67,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect @@ -80,7 +90,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/vsock v1.2.1 // indirect @@ -104,10 +114,10 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/alertmanager v0.28.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect + github.com/prometheus/common v0.67.4 // indirect github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/exporter-toolkit v0.14.0 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/shopspring/decimal v1.4.0 // indirect @@ -117,7 +127,7 @@ require ( github.com/stretchr/objx v0.5.2 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.mongodb.org/mongo-driver v1.17.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect @@ -130,49 +140,49 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.59.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect go.opentelemetry.io/otel/log v0.12.2 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect go.opentelemetry.io/otel/sdk/log v0.12.2 // indirect - go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools v0.40.0 // indirect golang.org/x/tools/godoc v0.1.0-deprecated // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/mail.v2 v2.3.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/client-go v0.34.2 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/client-go v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/alerting/historian/go.sum b/apps/alerting/historian/go.sum index e4440ed687f..6e82a1dea7b 100644 --- a/apps/alerting/historian/go.sum +++ b/apps/alerting/historian/go.sum @@ -63,8 +63,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= @@ -80,8 +80,8 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -123,16 +123,42 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/errors v0.22.3 h1:k6Hxa5Jg1TUyZnOwV2Lh81j8ayNw5VVYLvKrp4zFKFs= github.com/go-openapi/errors v0.22.3/go.mod h1:+WvbaBBULWCOna//9B9TbLNGSFOfF8lY9dw4hGiEiKQ= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= github.com/go-openapi/strfmt v0.24.0 h1:dDsopqbI3wrrlIzeXRbqMihRNnjzGC+ez4NQaAAJLuc= github.com/go-openapi/strfmt v0.24.0/go.mod h1:Lnn1Bk9rZjXxU9VMADbEEOo7D7CDyKGLsSKekhFr7s4= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -140,7 +166,6 @@ github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= @@ -187,8 +212,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -222,10 +247,10 @@ github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 h1:ZzG/gCclEit9w0 github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 h1:/5LKSYgLmAhwA4m6iGUD4w1YkydEWWjazn9qxCFT8W0= github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000/go.mod h1:/ZklAgE1i4f3Z8uriXwESmCr1VLF8lBGaJspuaGuf78= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= @@ -301,8 +326,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -376,8 +401,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg= @@ -387,8 +412,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -431,8 +456,8 @@ github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVK github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -457,8 +482,8 @@ go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuP go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 h1:oPW/SRFyHgIgxrvNhSBzqvZER2N5kRlci3/rGTOuyWo= go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0/go.mod h1:B9Oka5QVD0bnmZNO6gBbBta6nohD/1Z+f9waH2oXyBs= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= @@ -469,12 +494,12 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo= go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 h1:12vMqzLLNZtXuXbJhSENRg+Vvx+ynNilV8twBLBsXMY= @@ -486,20 +511,20 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7i go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0= go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -516,8 +541,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -552,8 +577,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -585,16 +610,16 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -605,8 +630,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -644,19 +669,19 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -706,8 +731,8 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk= golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -774,10 +799,10 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -804,8 +829,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= @@ -816,8 +841,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= @@ -840,26 +865,26 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/alerting/notifications/go.mod b/apps/alerting/notifications/go.mod index f72c4c9775b..b958cb377aa 100644 --- a/apps/alerting/notifications/go.mod +++ b/apps/alerting/notifications/go.mod @@ -3,21 +3,21 @@ module github.com/grafana/grafana/apps/alerting/notifications go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.5 - github.com/grafana/grafana-app-sdk/logging v0.48.3 - k8s.io/apimachinery v0.34.2 - k8s.io/apiserver v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + github.com/grafana/grafana-app-sdk v0.48.7 + github.com/grafana/grafana-app-sdk/logging v0.48.7 + k8s.io/apimachinery v0.34.3 + k8s.io/apiserver v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/coreos/go-systemd/v22 v22.6.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect @@ -26,14 +26,24 @@ require ( github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect @@ -44,7 +54,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -54,57 +64,56 @@ require ( github.com/onsi/ginkgo/v2 v2.22.2 // indirect github.com/onsi/gomega v1.36.2 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect - go.etcd.io/etcd/api/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/v3 v3.6.4 // indirect + go.etcd.io/etcd/api/v3 v3.6.6 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.6 // indirect + go.etcd.io/etcd/client/v3 v3.6.6 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/client-go v0.34.2 // indirect - k8s.io/component-base v0.34.2 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/client-go v0.34.3 // indirect + k8s.io/component-base v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/alerting/notifications/go.sum b/apps/alerting/notifications/go.sum index 57b099ab2f1..ea54639af1d 100644 --- a/apps/alerting/notifications/go.sum +++ b/apps/alerting/notifications/go.sum @@ -4,8 +4,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -13,8 +13,8 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -36,19 +36,44 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= @@ -60,8 +85,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -71,10 +96,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= @@ -104,8 +129,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -127,8 +152,6 @@ github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -140,11 +163,11 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1: github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -165,8 +188,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7 github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= @@ -175,12 +198,12 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= -go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= -go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= -go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= -go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= -go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= -go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= +go.etcd.io/etcd/api/v3 v3.6.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ= +go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4= +go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs= +go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI= +go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec= +go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8= go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA= go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE= go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU= @@ -189,26 +212,26 @@ go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -224,8 +247,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -236,30 +259,30 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -267,8 +290,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -279,20 +302,20 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= @@ -300,28 +323,28 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= -k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= -k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/alerting/rules/go.mod b/apps/alerting/rules/go.mod index 63da00536a6..45028a5bb0f 100644 --- a/apps/alerting/rules/go.mod +++ b/apps/alerting/rules/go.mod @@ -3,16 +3,16 @@ module github.com/grafana/grafana/apps/alerting/rules go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.5 - github.com/grafana/grafana-app-sdk/logging v0.48.3 - github.com/prometheus/common v0.67.3 - k8s.io/apimachinery v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + github.com/grafana/grafana-app-sdk v0.48.7 + github.com/grafana/grafana-app-sdk/logging v0.48.7 + github.com/prometheus/common v0.67.4 + k8s.io/apimachinery v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -22,13 +22,23 @@ require ( github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect @@ -37,7 +47,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -50,42 +60,42 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/client-go v0.34.2 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/client-go v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/alerting/rules/go.sum b/apps/alerting/rules/go.sum index 3df8e8b5ebe..e5233ba82c9 100644 --- a/apps/alerting/rules/go.sum +++ b/apps/alerting/rules/go.sum @@ -1,7 +1,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -23,14 +23,40 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= @@ -39,8 +65,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -48,10 +74,10 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -73,8 +99,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -95,8 +121,6 @@ github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -104,10 +128,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -122,30 +146,30 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -163,34 +187,34 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -199,39 +223,39 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0 gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/annotation/go.mod b/apps/annotation/go.mod index 946c42fe38f..aa4942ccd60 100644 --- a/apps/annotation/go.mod +++ b/apps/annotation/go.mod @@ -3,15 +3,15 @@ module github.com/grafana/grafana/apps/annotation go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.5 - github.com/grafana/grafana-app-sdk/logging v0.48.3 - k8s.io/apimachinery v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + github.com/grafana/grafana-app-sdk v0.48.7 + github.com/grafana/grafana-app-sdk/logging v0.48.7 + k8s.io/apimachinery v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -21,13 +21,23 @@ require ( github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect @@ -36,7 +46,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -49,43 +59,43 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/client-go v0.34.2 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/client-go v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/annotation/go.sum b/apps/annotation/go.sum index 3df8e8b5ebe..e5233ba82c9 100644 --- a/apps/annotation/go.sum +++ b/apps/annotation/go.sum @@ -1,7 +1,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -23,14 +23,40 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= @@ -39,8 +65,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -48,10 +74,10 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -73,8 +99,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -95,8 +121,6 @@ github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -104,10 +128,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -122,30 +146,30 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -163,34 +187,34 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -199,39 +223,39 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0 gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/collections/go.mod b/apps/collections/go.mod index 00575d7e6d4..05b9e33db99 100644 --- a/apps/collections/go.mod +++ b/apps/collections/go.mod @@ -3,11 +3,11 @@ module github.com/grafana/grafana/apps/collections go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.5 + github.com/grafana/grafana-app-sdk v0.48.7 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 github.com/stretchr/testify v1.11.1 - k8s.io/apimachinery v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + k8s.io/apimachinery v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( @@ -18,19 +18,29 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-logr/logr v1.4.3 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect - github.com/grafana/grafana-app-sdk/logging v0.48.3 // indirect + github.com/google/gnostic-models v0.7.1 // indirect + github.com/grafana/grafana-app-sdk/logging v0.48.7 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -41,28 +51,28 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/client-go v0.34.2 // indirect + k8s.io/client-go v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/collections/go.sum b/apps/collections/go.sum index 75a19848d73..aa5de8cbe99 100644 --- a/apps/collections/go.sum +++ b/apps/collections/go.sum @@ -14,29 +14,55 @@ github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 h1:X0cnaFdR+iz+sDSuoZmkryFSjOirchHe2MdKSRwBWgM= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:RRvSjHH12/PnQaXraMO65jUhVu8n59mzvhfIMBETnV4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -58,8 +84,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -76,8 +102,6 @@ github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletI github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -85,10 +109,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= @@ -101,16 +125,16 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -126,24 +150,24 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -154,31 +178,31 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/correlations/go.mod b/apps/correlations/go.mod index 29bc91e70a6..388afc2fd38 100644 --- a/apps/correlations/go.mod +++ b/apps/correlations/go.mod @@ -3,15 +3,15 @@ module github.com/grafana/grafana/apps/correlations go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.5 - github.com/grafana/grafana-app-sdk/logging v0.48.3 - k8s.io/apimachinery v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + github.com/grafana/grafana-app-sdk v0.48.7 + github.com/grafana/grafana-app-sdk/logging v0.48.7 + k8s.io/apimachinery v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -21,13 +21,23 @@ require ( github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect @@ -36,7 +46,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -49,43 +59,43 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/client-go v0.34.2 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/client-go v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/correlations/go.sum b/apps/correlations/go.sum index 3df8e8b5ebe..e5233ba82c9 100644 --- a/apps/correlations/go.sum +++ b/apps/correlations/go.sum @@ -1,7 +1,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -23,14 +23,40 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= @@ -39,8 +65,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -48,10 +74,10 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -73,8 +99,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -95,8 +121,6 @@ github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -104,10 +128,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -122,30 +146,30 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -163,34 +187,34 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -199,39 +223,39 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0 gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/dashboard/go.mod b/apps/dashboard/go.mod index 00fe99f0c9c..86d5ec1ff09 100644 --- a/apps/dashboard/go.mod +++ b/apps/dashboard/go.mod @@ -5,17 +5,17 @@ go 1.25.5 require ( cuelang.org/go v0.11.1 github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 - github.com/grafana/grafana-app-sdk v0.48.5 - github.com/grafana/grafana-app-sdk/logging v0.48.3 + github.com/grafana/grafana-app-sdk v0.48.7 + github.com/grafana/grafana-app-sdk/logging v0.48.7 github.com/grafana/grafana-plugin-sdk-go v0.284.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 - k8s.io/apimachinery v0.34.2 - k8s.io/apiserver v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 + k8s.io/apimachinery v0.34.3 + k8s.io/apiserver v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 ) require ( @@ -34,17 +34,27 @@ require ( github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect @@ -66,7 +76,7 @@ require ( github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/lib/pq v1.10.9 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/mattetti/filebuffer v1.0.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -80,55 +90,55 @@ require ( github.com/oklog/run v1.1.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // indirect go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools v0.40.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/client-go v0.34.2 // indirect - k8s.io/component-base v0.34.2 // indirect + k8s.io/client-go v0.34.3 // indirect + k8s.io/component-base v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/dashboard/go.sum b/apps/dashboard/go.sum index a93f97a1d0e..3bc70db5e6e 100644 --- a/apps/dashboard/go.sum +++ b/apps/dashboard/go.sum @@ -1,5 +1,5 @@ -cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 h1:R5wwEcbEZSBmeyg91MJZTxfd7WpBo2jPof3AYjRbxwY= -cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565/go.mod h1:5A4xfTzHTXfeVJBU6RAUf+QrlfTCW+017q/QiW+sMLg= +cuelabs.dev/go/oci/ociregistry v0.0.0-20251212221603-3adeb8663819 h1:Zh+Ur3OsoWpvALHPLT45nOekHkgOt+IOfutBbPqM17I= +cuelabs.dev/go/oci/ociregistry v0.0.0-20251212221603-3adeb8663819/go.mod h1:WjmQxb+W6nVNCgj8nXrF24lIz95AHwnSl36tpjDZSU8= cuelang.org/go v0.11.1 h1:pV+49MX1mmvDm8Qh3Za3M786cty8VKPWzQ1Ho4gZRP0= cuelang.org/go v0.11.1/go.mod h1:PBY6XvPUswPPJ2inpvUozP9mebDVTXaeehQikhZPBz0= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= @@ -28,8 +28,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY= -github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/emicklei/proto v1.14.2 h1:wJPxPy2Xifja9cEMrcA/g08art5+7CGJNFNk35iXC1I= +github.com/emicklei/proto v1.14.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= @@ -47,14 +47,40 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= @@ -71,8 +97,8 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -85,10 +111,10 @@ github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGr github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grafana/grafana-plugin-sdk-go v0.284.0 h1:1bK7eWsnPBLUWDcWJWe218Ik5ad0a5JpEL4mH9ry7Ws= github.com/grafana/grafana-plugin-sdk-go v0.284.0/go.mod h1:lHPniaSxq3SL5MxDIPy04TYB1jnTp/ivkYO+xn5Rz3E= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= @@ -142,8 +168,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -185,12 +211,12 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= @@ -204,12 +230,12 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq7lB31IeJL8iy7jkUmU/PG1Sr8jVGhS749dbUA= -github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/protocolbuffers/txtpbfmt v0.0.0-20251124094003-fcb97cc64c7b h1:fPVI9E6QNFYI0Ph3XpKUDrcAvbCifHvqYJcntFLPog8= +github.com/protocolbuffers/txtpbfmt v0.0.0-20251124094003-fcb97cc64c7b/go.mod h1:JSbkp0BviKovYYt9XunS95M3mLPibE9bGg+Y95DsEEY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -231,8 +257,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -243,34 +269,34 @@ github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 h1:2pn7OzMewmYRiNtv1doZnLo3gONcnMHlFnmOR8Vgt+8= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0/go.mod h1:rjbQTDEPQymPE0YnRQp9/NuPwwtL0sesz/fnqRW/v84= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo= go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 h1:oPW/SRFyHgIgxrvNhSBzqvZER2N5kRlci3/rGTOuyWo= go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0/go.mod h1:B9Oka5QVD0bnmZNO6gBbBta6nohD/1Z+f9waH2oXyBs= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -282,25 +308,25 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A= -golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -311,24 +337,24 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc h1:bH6xUXay0AIFMElXG2rQ4uiE+7ncwtiOdPfYK1NK2XA= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -337,42 +363,42 @@ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhS golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= -k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= -k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/example/go.mod b/apps/example/go.mod index d63d23be302..f2afba81283 100644 --- a/apps/example/go.mod +++ b/apps/example/go.mod @@ -3,17 +3,17 @@ module github.com/grafana/grafana/apps/example go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.5 - github.com/grafana/grafana-app-sdk/logging v0.48.3 + github.com/grafana/grafana-app-sdk v0.48.7 + github.com/grafana/grafana-app-sdk/logging v0.48.7 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20251017153501-8512b219c5fe - k8s.io/apimachinery v0.34.2 - k8s.io/apiserver v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + k8s.io/apimachinery v0.34.3 + k8s.io/apiserver v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -24,13 +24,23 @@ require ( github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect @@ -42,7 +52,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -56,43 +66,43 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/client-go v0.34.2 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/client-go v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/example/go.sum b/apps/example/go.sum index 70c83a9fe37..c5110f46829 100644 --- a/apps/example/go.sum +++ b/apps/example/go.sum @@ -1,7 +1,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -25,14 +25,40 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= @@ -41,8 +67,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -56,10 +82,10 @@ github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGr github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20251017153501-8512b219c5fe h1:pPoFj2bQKDBg5EyEdOU+Jn+0hQN+M775Qihk73RbdSs= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20251017153501-8512b219c5fe/go.mod h1:zn/yoxKpWA2KUsxOhQbSbL8OCkF2JNLpSEHs+hQYvdM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= @@ -83,8 +109,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -116,10 +142,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -134,30 +160,30 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -175,34 +201,34 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -211,41 +237,41 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0 gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= -k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/folder/go.mod b/apps/folder/go.mod index 476c6949c73..9a673364f89 100644 --- a/apps/folder/go.mod +++ b/apps/folder/go.mod @@ -3,10 +3,10 @@ module github.com/grafana/grafana/apps/folder go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.5 + github.com/grafana/grafana-app-sdk v0.48.7 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e - k8s.io/apimachinery v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + k8s.io/apimachinery v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( @@ -17,19 +17,29 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-logr/logr v1.4.3 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect - github.com/grafana/grafana-app-sdk/logging v0.48.3 // indirect + github.com/google/gnostic-models v0.7.1 // indirect + github.com/grafana/grafana-app-sdk/logging v0.48.7 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -40,29 +50,29 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.11.1 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/client-go v0.34.2 // indirect + k8s.io/client-go v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/folder/go.sum b/apps/folder/go.sum index d8454185267..3b414d3e3fe 100644 --- a/apps/folder/go.sum +++ b/apps/folder/go.sum @@ -14,29 +14,55 @@ github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:IA4SOwun8QyST9c5UNs/fN37XL6boXXDvRYFcFwbipg= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -58,8 +84,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -76,8 +102,6 @@ github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletI github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -85,10 +109,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= @@ -101,16 +125,16 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -126,24 +150,24 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -154,31 +178,31 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/iam/go.mod b/apps/iam/go.mod index c741eb97423..24769ca825f 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -52,19 +52,19 @@ replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-aler require ( github.com/grafana/grafana v0.0.0-00010101000000-000000000000 - github.com/grafana/grafana-app-sdk v0.48.5 - github.com/grafana/grafana-app-sdk/logging v0.48.3 + github.com/grafana/grafana-app-sdk v0.48.7 + github.com/grafana/grafana-app-sdk/logging v0.48.7 github.com/grafana/grafana/apps/folder v0.0.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0 github.com/prometheus/client_golang v1.23.2 - go.opentelemetry.io/otel v1.38.0 - go.opentelemetry.io/otel/trace v1.38.0 - k8s.io/apimachinery v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + go.opentelemetry.io/otel v1.39.0 + go.opentelemetry.io/otel/trace v1.39.0 + k8s.io/apimachinery v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.1 // indirect cloud.google.com/go v0.121.4 // indirect cloud.google.com/go/auth v0.16.3 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect @@ -131,7 +131,7 @@ require ( github.com/blang/semver v3.5.1+incompatible // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/bluele/gcache v0.0.2 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/bufbuild/protocompile v0.14.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/bwmarrin/snowflake v0.3.0 // indirect @@ -143,7 +143,7 @@ require ( github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/coreos/go-semver v0.3.1 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/coreos/go-systemd/v22 v22.6.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dennwc/varint v1.0.0 // indirect github.com/dgraph-io/badger/v4 v4.7.0 // indirect @@ -178,22 +178,24 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.24.0 // indirect github.com/go-openapi/errors v0.22.3 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect github.com/go-openapi/loads v0.23.1 // indirect github.com/go-openapi/runtime v0.28.0 // indirect github.com/go-openapi/spec v0.22.0 // indirect github.com/go-openapi/strfmt v0.24.0 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/conv v0.25.1 // indirect - github.com/go-openapi/swag/fileutils v0.25.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect - github.com/go-openapi/swag/jsonutils v0.25.1 // indirect - github.com/go-openapi/swag/loading v0.25.1 // indirect - github.com/go-openapi/swag/mangling v0.25.1 // indirect - github.com/go-openapi/swag/stringutils v0.25.1 // indirect - github.com/go-openapi/swag/typeutils v0.25.1 // indirect - github.com/go-openapi/swag/yamlutils v0.25.1 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-openapi/validate v0.25.0 // indirect github.com/go-sql-driver/mysql v1.9.3 // indirect github.com/go-stack/stack v1.8.1 // indirect @@ -212,7 +214,7 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/cel-go v0.26.1 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/s2a-go v0.1.9 // indirect @@ -285,7 +287,7 @@ require ( github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lestrrat-go/strftime v1.0.4 // indirect github.com/lib/pq v1.10.9 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 // indirect github.com/mattetti/filebuffer v1.0.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect @@ -339,10 +341,10 @@ require ( github.com/pressly/goose/v3 v3.26.0 // indirect github.com/prometheus/alertmanager v0.28.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect + github.com/prometheus/common v0.67.4 // indirect github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/exporter-toolkit v0.14.0 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/prometheus/prometheus v0.303.1 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/redis/go-redis/v9 v9.14.0 // indirect @@ -359,7 +361,7 @@ require ( github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/viper v1.21.0 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect @@ -373,20 +375,20 @@ require ( github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect - go.etcd.io/etcd/api/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/v3 v3.6.4 // indirect + go.etcd.io/etcd/api/v3 v3.6.6 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.6 // indirect + go.etcd.io/etcd/client/v3 v3.6.6 // indirect go.mongodb.org/mongo-driver v1.17.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/bridges/prometheus v0.61.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.38.0 // indirect go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // indirect go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 // indirect go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect @@ -394,18 +396,18 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.59.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect go.opentelemetry.io/otel/log v0.12.2 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect go.opentelemetry.io/otel/sdk/log v0.12.2 // indirect - go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/mock v0.6.0 // indirect @@ -414,30 +416,30 @@ require ( go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect gocloud.dev v0.43.0 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools v0.40.0 // indirect golang.org/x/tools/godoc v0.1.0-deprecated // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect gonum.org/v1/gonum v0.16.0 // indirect google.golang.org/api v0.242.0 // indirect google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/mail.v2 v2.3.1 // indirect @@ -446,20 +448,20 @@ require ( gopkg.in/telebot.v3 v3.3.8 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/apiserver v0.34.2 // indirect - k8s.io/client-go v0.34.2 // indirect - k8s.io/component-base v0.34.2 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/apiserver v0.34.3 // indirect + k8s.io/client-go v0.34.3 // indirect + k8s.io/component-base v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kms v0.34.2 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect + k8s.io/kms v0.34.3 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect modernc.org/libc v1.66.10 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect modernc.org/sqlite v1.40.1 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/iam/go.sum b/apps/iam/go.sum index 4c8ec5e38b5..e9c440860de 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -2,8 +2,8 @@ buf.build/gen/go/parca-dev/parca/connectrpc/go v1.18.1-20250703125925-3f0fcf4bff buf.build/gen/go/parca-dev/parca/connectrpc/go v1.18.1-20250703125925-3f0fcf4bff96.1/go.mod h1:pjl83IqpNF7Lm/lOPMLQ5IIfCg+yTu2A9OgYTcEncCs= buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.36.2-20250703125925-3f0fcf4bff96.1 h1:9nqE/pDc/HXAXiD5pZncPywjAzWgKuBkFFYgdK2lVU8= buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.36.2-20250703125925-3f0fcf4bff96.1/go.mod h1:1M7nlq2ljfzb95x9LaA2j1gYIvDkVZii58mGvTa9ExM= -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= @@ -84,8 +84,8 @@ cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4 cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw= connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= -cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 h1:R5wwEcbEZSBmeyg91MJZTxfd7WpBo2jPof3AYjRbxwY= -cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565/go.mod h1:5A4xfTzHTXfeVJBU6RAUf+QrlfTCW+017q/QiW+sMLg= +cuelabs.dev/go/oci/ociregistry v0.0.0-20251212221603-3adeb8663819 h1:Zh+Ur3OsoWpvALHPLT45nOekHkgOt+IOfutBbPqM17I= +cuelabs.dev/go/oci/ociregistry v0.0.0-20251212221603-3adeb8663819/go.mod h1:WjmQxb+W6nVNCgj8nXrF24lIz95AHwnSl36tpjDZSU8= cuelang.org/go v0.11.1 h1:pV+49MX1mmvDm8Qh3Za3M786cty8VKPWzQ1Ho4gZRP0= cuelang.org/go v0.11.1/go.mod h1:PBY6XvPUswPPJ2inpvUozP9mebDVTXaeehQikhZPBz0= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= @@ -358,8 +358,8 @@ github.com/blugelabs/ice v1.0.0/go.mod h1:gNfFPk5zM+yxJROhthxhVQYjpBO9amuxWXJQ2L github.com/blugelabs/ice/v2 v2.0.1 h1:mzHbntLjk2v7eDRgoXCgzOsPKN1Tenu9Svo6l9cTLS4= github.com/blugelabs/ice/v2 v2.0.1/go.mod h1:QxAWSPNwZwsIqS25c3lbIPFQrVvT1sphf5x5DfMLH5M= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -428,8 +428,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -510,8 +510,8 @@ github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY= -github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/emicklei/proto v1.14.2 h1:wJPxPy2Xifja9cEMrcA/g08art5+7CGJNFNk35iXC1I= +github.com/emicklei/proto v1.14.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -597,10 +597,10 @@ github.com/go-openapi/analysis v0.24.0 h1:vE/VFFkICKyYuTWYnplQ+aVr45vlG6NcZKC7Bd github.com/go-openapi/analysis v0.24.0/go.mod h1:GLyoJA+bvmGGaHgpfeDh8ldpGo69fAJg7eeMDMRCIrw= github.com/go-openapi/errors v0.22.3 h1:k6Hxa5Jg1TUyZnOwV2Lh81j8ayNw5VVYLvKrp4zFKFs= github.com/go-openapi/errors v0.22.3/go.mod h1:+WvbaBBULWCOna//9B9TbLNGSFOfF8lY9dw4hGiEiKQ= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= github.com/go-openapi/loads v0.23.1 h1:H8A0dX2KDHxDzc797h0+uiCZ5kwE2+VojaQVaTlXvS0= github.com/go-openapi/loads v0.23.1/go.mod h1:hZSXkyACCWzWPQqizAv/Ye0yhi2zzHwMmoXQ6YQml44= github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= @@ -609,28 +609,36 @@ github.com/go-openapi/spec v0.22.0 h1:xT/EsX4frL3U09QviRIZXvkh80yibxQmtoEvyqug0T github.com/go-openapi/spec v0.22.0/go.mod h1:K0FhKxkez8YNS94XzF8YKEMULbFrRw4m15i2YUht4L0= github.com/go-openapi/strfmt v0.24.0 h1:dDsopqbI3wrrlIzeXRbqMihRNnjzGC+ez4NQaAAJLuc= github.com/go-openapi/strfmt v0.24.0/go.mod h1:Lnn1Bk9rZjXxU9VMADbEEOo7D7CDyKGLsSKekhFr7s4= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0= -github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs= -github.com/go-openapi/swag/fileutils v0.25.1 h1:rSRXapjQequt7kqalKXdcpIegIShhTPXx7yw0kek2uU= -github.com/go-openapi/swag/fileutils v0.25.1/go.mod h1:+NXtt5xNZZqmpIpjqcujqojGFek9/w55b3ecmOdtg8M= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= -github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8= -github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg= -github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw= -github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc= -github.com/go-openapi/swag/mangling v0.25.1 h1:XzILnLzhZPZNtmxKaz/2xIGPQsBsvmCjrJOWGNz/ync= -github.com/go-openapi/swag/mangling v0.25.1/go.mod h1:CdiMQ6pnfAgyQGSOIYnZkXvqhnnwOn997uXZMAd/7mQ= -github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw= -github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg= -github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA= -github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8= -github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk= -github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-openapi/validate v0.25.0 h1:JD9eGX81hDTjoY3WOzh6WqxVBVl7xjsLnvDo1GL5WPU= github.com/go-openapi/validate v0.25.0/go.mod h1:SUY7vKrN5FiwK6LyvSwKjDfLNirSfWwHNgxd2l29Mmw= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -737,8 +745,8 @@ github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -837,10 +845,10 @@ github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f h1:5xkjl5Y/j2QefJKO github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f/go.mod h1:+O5QxOwwgP10jedZHapzXY+IPKTnzHBtIs5UUb9G+kI= github.com/grafana/gomemcache v0.0.0-20250828162811-a96f6acee2fe h1:q+QaVANzNZxvTovycpQvDTfsNZ2rHh4XIIaccMnrIR4= github.com/grafana/gomemcache v0.0.0-20250828162811-a96f6acee2fe/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grafana/grafana-aws-sdk v1.3.0 h1:/bfJzP93rCel1GbWoRSq0oUo424MZXt8jAp2BK9w8tM= github.com/grafana/grafana-aws-sdk v1.3.0/go.mod h1:VGycF0JkCGKND2O5je1ucOqPJ0ZNhZYzV3c2bNBAaGk= github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 h1:FFcEA01tW+SmuJIuDbHOdgUBL+d7DPrZ2N4zwzPhfGk= @@ -1104,8 +1112,8 @@ github.com/madflojo/testcerts v1.4.0/go.mod h1:MW8sh39gLnkKh4K0Nc55AyHEDl9l/FBLD github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 h1:hQWBtNqRYrI7CWIaUSXXtNKR90KzcUA5uiuxFVWw7sU= github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU= @@ -1353,8 +1361,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg= @@ -1366,14 +1374,14 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/prometheus/prometheus v0.303.1 h1:He/2jRE6sB23Ew38AIoR1WRR3fCMgPlJA2E0obD2WSY= github.com/prometheus/prometheus v0.303.1/go.mod h1:WEq2ogBPZoLjj9x5K67VEk7ECR0nRD9XCjaOt1lsYck= github.com/prometheus/sigv4 v0.1.2 h1:R7570f8AoM5YnTUPFm3mjZH5q2k4D+I/phCWvZ4PXG8= github.com/prometheus/sigv4 v0.1.2/go.mod h1:GF9fwrvLgkQwDdQ5BXeV9XUSCH/IPNqzvAoaohfjqMU= -github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq7lB31IeJL8iy7jkUmU/PG1Sr8jVGhS749dbUA= -github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= +github.com/protocolbuffers/txtpbfmt v0.0.0-20251124094003-fcb97cc64c7b h1:fPVI9E6QNFYI0Ph3XpKUDrcAvbCifHvqYJcntFLPog8= +github.com/protocolbuffers/txtpbfmt v0.0.0-20251124094003-fcb97cc64c7b/go.mod h1:JSbkp0BviKovYYt9XunS95M3mLPibE9bGg+Y95DsEEY= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0= @@ -1450,8 +1458,8 @@ github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8 github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -1525,8 +1533,8 @@ github.com/wk8/go-ordered-map v1.0.0 h1:BV7z+2PaK8LTSd/mWgY12HyMAo5CEgkHqbkVq2th github.com/wk8/go-ordered-map v1.0.0/go.mod h1:9ZIbRunKbuvfPKyBP1SIKLcXNlv74YCOZ3t3VTS6gRk= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= @@ -1557,15 +1565,15 @@ gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2 go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= -go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= -go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= +go.etcd.io/etcd/api/v3 v3.6.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ= +go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4= go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= -go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= +go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs= +go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI= go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= -go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= -go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= +go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec= +go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8= go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA= go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE= go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU= @@ -1597,19 +1605,19 @@ go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 h1:XfzKtKSrbtYk9TNCF8dkO0Y9M7IOfb4idCwBOTwGBiI= go.opentelemetry.io/contrib/exporters/autoexport v0.61.0/go.mod h1:N6otC+qXTD5bAnbK2O1f/1SXq3cX+3KYSWrkBUqG0cw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 h1:2pn7OzMewmYRiNtv1doZnLo3gONcnMHlFnmOR8Vgt+8= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0/go.mod h1:rjbQTDEPQymPE0YnRQp9/NuPwwtL0sesz/fnqRW/v84= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo= go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 h1:oPW/SRFyHgIgxrvNhSBzqvZER2N5kRlci3/rGTOuyWo= go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0/go.mod h1:B9Oka5QVD0bnmZNO6gBbBta6nohD/1Z+f9waH2oXyBs= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= @@ -1620,12 +1628,12 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo= go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 h1:12vMqzLLNZtXuXbJhSENRg+Vvx+ynNilV8twBLBsXMY= @@ -1637,20 +1645,20 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7i go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0= go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= @@ -1692,8 +1700,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1707,8 +1715,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A= -golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1735,8 +1743,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1791,8 +1799,8 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1815,8 +1823,8 @@ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1829,8 +1837,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1920,14 +1928,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc h1:bH6xUXay0AIFMElXG2rQ4uiE+7ncwtiOdPfYK1NK2XA= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1937,8 +1945,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2005,8 +2013,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk= golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2159,10 +2167,10 @@ google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 h1:Nt6z9UHqSlIdIGJdz6KhTIs2VRx/iOsA5iE8bmQNcxs= google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79/go.mod h1:kTmlBHMPqR5uCZPBvwa2B18mvubkjyY3CRLI0c6fj0s= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -2213,8 +2221,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= @@ -2225,8 +2233,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -2263,28 +2271,28 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= -k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= -k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kms v0.34.2 h1:91rj4MDZLyIT9KxG8J5/CcMH666Z88CF/xJQeuPfJc8= -k8s.io/kms v0.34.2/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= +k8s.io/kms v0.34.3 h1:QzBOD0sk1bGQVMcZQAHGjtbP1iKZJUyhC6D0I+BTxIE= +k8s.io/kms v0.34.3/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= k8s.io/kube-aggregator v0.34.2 h1:Nn0Vksj67WHBL2x7bJ6vuxL44RbMTK6uRtXX+3vMVJk= k8s.io/kube-aggregator v0.34.2/go.mod h1:/tp4cc/1p2AvICsS4mjjSJakdrbhcGbRmj0mdHTdR2Q= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4= modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A= @@ -2315,10 +2323,10 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/investigations/go.mod b/apps/investigations/go.mod index 3844fec2689..40677b56dab 100644 --- a/apps/investigations/go.mod +++ b/apps/investigations/go.mod @@ -3,15 +3,15 @@ module github.com/grafana/grafana/apps/investigations go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.5 - k8s.io/apimachinery v0.34.2 + github.com/grafana/grafana-app-sdk v0.48.7 + k8s.io/apimachinery v0.34.3 k8s.io/klog/v2 v2.130.1 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -21,23 +21,33 @@ require ( github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grafana/grafana-app-sdk/logging v0.48.3 // indirect + github.com/grafana/grafana-app-sdk/logging v0.48.7 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -50,42 +60,42 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/client-go v0.34.2 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/client-go v0.34.3 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/investigations/go.sum b/apps/investigations/go.sum index 3df8e8b5ebe..e5233ba82c9 100644 --- a/apps/investigations/go.sum +++ b/apps/investigations/go.sum @@ -1,7 +1,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -23,14 +23,40 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= @@ -39,8 +65,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -48,10 +74,10 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -73,8 +99,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -95,8 +121,6 @@ github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -104,10 +128,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -122,30 +146,30 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -163,34 +187,34 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -199,39 +223,39 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0 gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/logsdrilldown/go.mod b/apps/logsdrilldown/go.mod index 2a35278d19a..e210773eb2e 100644 --- a/apps/logsdrilldown/go.mod +++ b/apps/logsdrilldown/go.mod @@ -3,15 +3,15 @@ module github.com/grafana/grafana/apps/logsdrilldown go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.5 - github.com/grafana/grafana-app-sdk/logging v0.48.3 - k8s.io/apimachinery v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + github.com/grafana/grafana-app-sdk v0.48.7 + github.com/grafana/grafana-app-sdk/logging v0.48.7 + k8s.io/apimachinery v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -21,13 +21,23 @@ require ( github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect @@ -36,7 +46,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -49,43 +59,43 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/client-go v0.34.2 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/client-go v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/logsdrilldown/go.sum b/apps/logsdrilldown/go.sum index 3df8e8b5ebe..e5233ba82c9 100644 --- a/apps/logsdrilldown/go.sum +++ b/apps/logsdrilldown/go.sum @@ -1,7 +1,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -23,14 +23,40 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= @@ -39,8 +65,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -48,10 +74,10 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -73,8 +99,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -95,8 +121,6 @@ github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -104,10 +128,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -122,30 +146,30 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -163,34 +187,34 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -199,39 +223,39 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0 gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/playlist/go.mod b/apps/playlist/go.mod index ae38e3d0cd3..dd749f9fede 100644 --- a/apps/playlist/go.mod +++ b/apps/playlist/go.mod @@ -3,16 +3,16 @@ module github.com/grafana/grafana/apps/playlist go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.5 - k8s.io/apimachinery v0.34.2 - k8s.io/client-go v0.34.2 + github.com/grafana/grafana-app-sdk v0.48.7 + k8s.io/apimachinery v0.34.3 + k8s.io/client-go v0.34.3 k8s.io/klog/v2 v2.130.1 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -22,23 +22,33 @@ require ( github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grafana/grafana-app-sdk/logging v0.48.3 // indirect + github.com/grafana/grafana-app-sdk/logging v0.48.7 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -51,41 +61,41 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/playlist/go.sum b/apps/playlist/go.sum index 3df8e8b5ebe..e5233ba82c9 100644 --- a/apps/playlist/go.sum +++ b/apps/playlist/go.sum @@ -1,7 +1,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -23,14 +23,40 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= @@ -39,8 +65,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -48,10 +74,10 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -73,8 +99,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -95,8 +121,6 @@ github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -104,10 +128,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -122,30 +146,30 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -163,34 +187,34 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -199,39 +223,39 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0 gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/plugins/go.mod b/apps/plugins/go.mod index 5341081d027..e2084f9b8d8 100644 --- a/apps/plugins/go.mod +++ b/apps/plugins/go.mod @@ -11,18 +11,18 @@ replace github.com/grafana/grafana/pkg/apiserver => ../../pkg/apiserver require ( github.com/emicklei/go-restful/v3 v3.13.0 github.com/grafana/grafana v0.0.0-00010101000000-000000000000 - github.com/grafana/grafana-app-sdk v0.48.5 - github.com/grafana/grafana-app-sdk/logging v0.48.3 + github.com/grafana/grafana-app-sdk v0.48.7 + github.com/grafana/grafana-app-sdk/logging v0.48.7 github.com/stretchr/testify v1.11.1 - k8s.io/apimachinery v0.34.2 - k8s.io/apiserver v0.34.2 - k8s.io/client-go v0.34.2 + k8s.io/apimachinery v0.34.3 + k8s.io/apiserver v0.34.3 + k8s.io/client-go v0.34.3 k8s.io/klog/v2 v2.130.1 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.1 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/apache/arrow-go/v18 v18.4.1 // indirect @@ -37,13 +37,13 @@ require ( github.com/aws/smithy-go v1.23.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/bwmarrin/snowflake v0.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/coreos/go-systemd/v22 v22.6.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/fatih/color v1.18.0 // indirect @@ -56,10 +56,20 @@ require ( github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gobwas/glob v0.2.3 // indirect @@ -71,7 +81,7 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/cel-go v0.26.1 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 // indirect @@ -113,7 +123,7 @@ require ( github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/mattetti/filebuffer v1.0.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -145,72 +155,72 @@ require ( github.com/prometheus/alertmanager v0.28.2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect + github.com/prometheus/common v0.67.4 // indirect github.com/prometheus/exporter-toolkit v0.14.0 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect - github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stoewer/go-strcase v1.3.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/tjhop/slog-gokit v0.1.5 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect - go.etcd.io/etcd/api/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/v3 v3.6.4 // indirect + go.etcd.io/etcd/api/v3 v3.6.6 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.6 // indirect + go.etcd.io/etcd/client/v3 v3.6.6 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // indirect go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools v0.40.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/component-base v0.34.2 // indirect - k8s.io/kms v0.34.2 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/component-base v0.34.3 // indirect + k8s.io/kms v0.34.3 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/plugins/go.sum b/apps/plugins/go.sum index 7d58d87be04..dcbf9d3a129 100644 --- a/apps/plugins/go.sum +++ b/apps/plugins/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= @@ -44,8 +44,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= @@ -62,8 +62,8 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -106,14 +106,40 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -127,7 +153,6 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -157,8 +182,8 @@ github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -184,10 +209,10 @@ github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6k github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grafana/grafana-aws-sdk v1.3.0 h1:/bfJzP93rCel1GbWoRSq0oUo424MZXt8jAp2BK9w8tM= github.com/grafana/grafana-aws-sdk v1.3.0/go.mod h1:VGycF0JkCGKND2O5je1ucOqPJ0ZNhZYzV3c2bNBAaGk= github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 h1:FFcEA01tW+SmuJIuDbHOdgUBL+d7DPrZ2N4zwzPhfGk= @@ -286,8 +311,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -388,8 +413,8 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/exporter-toolkit v0.14.0 h1:NMlswfibpcZZ+H0sZBiTjrA3/aBFHkNZqE+iCj5EmRg= github.com/prometheus/exporter-toolkit v0.14.0/go.mod h1:Gu5LnVvt7Nr/oqTBUC23WILZepW0nffNo10XdhQcwWA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -398,8 +423,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -417,8 +442,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -447,8 +472,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= @@ -461,12 +486,12 @@ github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= -go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= -go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= -go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= -go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= -go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= -go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= +go.etcd.io/etcd/api/v3 v3.6.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ= +go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4= +go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs= +go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI= +go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec= +go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8= go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA= go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE= go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU= @@ -475,36 +500,36 @@ go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 h1:2pn7OzMewmYRiNtv1doZnLo3gONcnMHlFnmOR8Vgt+8= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0/go.mod h1:rjbQTDEPQymPE0YnRQp9/NuPwwtL0sesz/fnqRW/v84= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo= go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 h1:oPW/SRFyHgIgxrvNhSBzqvZER2N5kRlci3/rGTOuyWo= go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0/go.mod h1:B9Oka5QVD0bnmZNO6gBbBta6nohD/1Z+f9waH2oXyBs= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -521,15 +546,15 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A= -golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -542,12 +567,12 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -555,8 +580,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -578,17 +603,17 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc h1:bH6xUXay0AIFMElXG2rQ4uiE+7ncwtiOdPfYK1NK2XA= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -596,8 +621,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -611,10 +636,10 @@ gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= @@ -625,15 +650,15 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= @@ -651,30 +676,30 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= -k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= -k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kms v0.34.2 h1:91rj4MDZLyIT9KxG8J5/CcMH666Z88CF/xJQeuPfJc8= -k8s.io/kms v0.34.2/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kms v0.34.3 h1:QzBOD0sk1bGQVMcZQAHGjtbP1iKZJUyhC6D0I+BTxIE= +k8s.io/kms v0.34.3/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/preferences/go.mod b/apps/preferences/go.mod index 661002a3b59..520609347ae 100644 --- a/apps/preferences/go.mod +++ b/apps/preferences/go.mod @@ -3,10 +3,10 @@ module github.com/grafana/grafana/apps/preferences go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.5 + github.com/grafana/grafana-app-sdk v0.48.7 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 - k8s.io/apimachinery v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + k8s.io/apimachinery v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( @@ -17,19 +17,29 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-logr/logr v1.4.3 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect - github.com/grafana/grafana-app-sdk/logging v0.48.3 // indirect + github.com/google/gnostic-models v0.7.1 // indirect + github.com/grafana/grafana-app-sdk/logging v0.48.7 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -40,29 +50,29 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.11.1 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/client-go v0.34.2 // indirect + k8s.io/client-go v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/preferences/go.sum b/apps/preferences/go.sum index 75a19848d73..aa5de8cbe99 100644 --- a/apps/preferences/go.sum +++ b/apps/preferences/go.sum @@ -14,29 +14,55 @@ github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 h1:X0cnaFdR+iz+sDSuoZmkryFSjOirchHe2MdKSRwBWgM= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:RRvSjHH12/PnQaXraMO65jUhVu8n59mzvhfIMBETnV4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -58,8 +84,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -76,8 +102,6 @@ github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletI github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -85,10 +109,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= @@ -101,16 +125,16 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -126,24 +150,24 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -154,31 +178,31 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/provisioning/go.mod b/apps/provisioning/go.mod index 3cccd7dd8be..c82b90c4dcc 100644 --- a/apps/provisioning/go.mod +++ b/apps/provisioning/go.mod @@ -7,17 +7,17 @@ require ( github.com/google/go-github/v70 v70.0.0 github.com/google/uuid v1.6.0 github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f - github.com/grafana/grafana-app-sdk/logging v0.48.3 + github.com/grafana/grafana-app-sdk/logging v0.48.7 github.com/grafana/grafana/apps/secret v0.0.0-20250902093454-b56b7add012f github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 github.com/grafana/nanogit v0.3.0 github.com/migueleliasweb/go-github-mock v1.1.0 github.com/stretchr/testify v1.11.1 - golang.org/x/oauth2 v0.33.0 - k8s.io/apimachinery v0.34.2 - k8s.io/apiserver v0.34.2 - k8s.io/client-go v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + golang.org/x/oauth2 v0.34.0 + k8s.io/apimachinery v0.34.3 + k8s.io/apiserver v0.34.3 + k8s.io/client-go v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e sigs.k8s.io/structured-merge-diff/v6 v6.3.1 ) @@ -32,60 +32,68 @@ require ( github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-github/v64 v64.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // indirect github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect - github.com/grafana/grafana-app-sdk v0.48.5 // indirect - github.com/josharian/intern v1.0.0 // indirect + github.com/grafana/grafana-app-sdk v0.48.7 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect - github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/component-base v0.34.2 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/component-base v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/apps/provisioning/go.sum b/apps/provisioning/go.sum index 0e1ba180fad..39f1b7f2064 100644 --- a/apps/provisioning/go.sum +++ b/apps/provisioning/go.sum @@ -23,22 +23,48 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -62,18 +88,16 @@ github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGr github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grafana/grafana/apps/secret v0.0.0-20250902093454-b56b7add012f h1:f+Z5Xpfp1WNYjUe23ginerWsHWUsRgOWrr3WGu3SlWs= github.com/grafana/grafana/apps/secret v0.0.0-20250902093454-b56b7add012f/go.mod h1:RA8mP8KVIwKXBx3Ssqa/uEBABib5LvUWYPVMxrNvnP0= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 h1:X0cnaFdR+iz+sDSuoZmkryFSjOirchHe2MdKSRwBWgM= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:RRvSjHH12/PnQaXraMO65jUhVu8n59mzvhfIMBETnV4= github.com/grafana/nanogit v0.3.0 h1:XNEef+4Vi+465ZITJs/g/xgnDRJbWhhJ7iQrAnWZ0oQ= github.com/grafana/nanogit v0.3.0/go.mod h1:6s6CCTpyMOHPpcUZaLGI+rgBEKdmxVbhqSGgCK13j7Y= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -86,8 +110,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/migueleliasweb/go-github-mock v1.1.0 h1:GKaOBPsrPGkAKgtfuWY8MclS1xR6MInkx1SexJucMwE= github.com/migueleliasweb/go-github-mock v1.1.0/go.mod h1:pYe/XlGs4BGMfRY4vmeixVsODHnVDDhJ9zoi0qzSMHc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -113,10 +135,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= @@ -135,16 +157,16 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -156,8 +178,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -170,17 +192,17 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -191,23 +213,23 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -216,48 +238,48 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= -k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= -k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/quotas/go.mod b/apps/quotas/go.mod index 1ab18fe8876..857521f2c57 100644 --- a/apps/quotas/go.mod +++ b/apps/quotas/go.mod @@ -3,15 +3,15 @@ module github.com/grafana/grafana/apps/quotas go 1.25.3 require ( - github.com/grafana/grafana-app-sdk v0.48.5 - github.com/grafana/grafana-app-sdk/logging v0.48.3 - k8s.io/apimachinery v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + github.com/grafana/grafana-app-sdk v0.48.7 + github.com/grafana/grafana-app-sdk/logging v0.48.7 + k8s.io/apimachinery v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -21,22 +21,22 @@ require ( github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -49,43 +49,43 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect - go.opentelemetry.io/proto/otlp v1.7.1 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/client-go v0.34.2 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/client-go v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/quotas/go.sum b/apps/quotas/go.sum index 5787fa55023..f331bdd9beb 100644 --- a/apps/quotas/go.sum +++ b/apps/quotas/go.sum @@ -2,6 +2,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -25,12 +26,16 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= @@ -41,6 +46,7 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -54,11 +60,16 @@ github.com/grafana/grafana-app-sdk v0.48.4 h1:t9r+Y6E7D832ZxQ2c1n0lp6cvsYKhhrAod github.com/grafana/grafana-app-sdk v0.48.4/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= github.com/grafana/grafana-app-sdk/logging v0.48.1 h1:veM0X5LAPyN3KsDLglWjIofndbGuf7MqnrDuDN+F/Ng= github.com/grafana/grafana-app-sdk/logging v0.48.1/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -80,6 +91,7 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -111,8 +123,10 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= @@ -129,6 +143,7 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -138,22 +153,32 @@ go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -171,24 +196,30 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -209,15 +240,18 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1: google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw= google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 h1:CirRxTOwnRWVLKzDNrs0CXAaVozJoR4G9xvdRecrdpk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -229,20 +263,27 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= diff --git a/apps/scope/go.mod b/apps/scope/go.mod index c54a582379f..ed380519e75 100644 --- a/apps/scope/go.mod +++ b/apps/scope/go.mod @@ -4,8 +4,8 @@ go 1.25.5 require ( github.com/grafana/grafana/pkg/apimachinery v0.0.0-20251007081214-26e147d01f0a - k8s.io/apimachinery v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + k8s.io/apimachinery v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( @@ -13,15 +13,24 @@ require ( github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect - github.com/josharian/intern v1.0.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -30,14 +39,15 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/text v0.31.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/text v0.32.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect ) diff --git a/apps/scope/go.sum b/apps/scope/go.sum index 5df9519a7e2..19f7f445bc6 100644 --- a/apps/scope/go.sum +++ b/apps/scope/go.sum @@ -1,3 +1,4 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -8,44 +9,71 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20251007081214-26e147d01f0a h1:L7xgV9mP6MRF3L2/vDOjNR7heaBPbXPMGTDN9/jXSFQ= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20251007081214-26e147d01f0a/go.mod h1:OK8NwS87D5YphchOcAsiIWk/feMZ0EzfAGME1Kff860= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= @@ -73,8 +101,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -83,8 +111,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -93,8 +121,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -102,16 +130,16 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/sdk.mk b/apps/sdk.mk index 68cf4c1c9c9..45580f59037 100644 --- a/apps/sdk.mk +++ b/apps/sdk.mk @@ -1,4 +1,4 @@ -APP_SDK_VERSION = v0.48.5 +APP_SDK_VERSION = v0.48.7 APP_SDK_DIR = $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION) APP_SDK_BIN = $(APP_SDK_DIR)/grafana-app-sdk diff --git a/apps/secret/go.mod b/apps/secret/go.mod index a1ed48be6e7..d0da36e2e83 100644 --- a/apps/secret/go.mod +++ b/apps/secret/go.mod @@ -3,15 +3,15 @@ module github.com/grafana/grafana/apps/secret go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.5 + github.com/grafana/grafana-app-sdk v0.48.7 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250710134100-1f3dc0533caf github.com/stretchr/testify v1.11.1 go.yaml.in/yaml/v3 v3.0.4 google.golang.org/grpc v1.77.0 - google.golang.org/protobuf v1.36.10 - k8s.io/apimachinery v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 + google.golang.org/protobuf v1.36.11 + k8s.io/apimachinery v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 ) require ( @@ -22,19 +22,29 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-logr/logr v1.4.3 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect - github.com/grafana/grafana-app-sdk/logging v0.48.3 // indirect + github.com/google/gnostic-models v0.7.1 // indirect + github.com/grafana/grafana-app-sdk/logging v0.48.7 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -45,26 +55,27 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/client-go v0.34.2 // indirect + k8s.io/client-go v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/secret/go.sum b/apps/secret/go.sum index 166e281f5af..6ea5dd8dfd3 100644 --- a/apps/secret/go.sum +++ b/apps/secret/go.sum @@ -16,31 +16,57 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250710134100-1f3dc0533caf h1:BBGDHffvVNLoYQlXEpbXcxE0vbpq7pm/8OWF5I+UDZg= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250710134100-1f3dc0533caf/go.mod h1:eAlOam2uWhrsEZlOoAr7XZ9hbBP7SyYGYn31/aQAPs8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -62,8 +88,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -80,8 +106,6 @@ github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletI github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -89,10 +113,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= @@ -105,24 +129,24 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -138,24 +162,24 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -168,35 +192,35 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/apps/shorturl/go.mod b/apps/shorturl/go.mod index 4a414de5b4e..c75106d2822 100644 --- a/apps/shorturl/go.mod +++ b/apps/shorturl/go.mod @@ -3,18 +3,18 @@ module github.com/grafana/grafana/apps/shorturl go 1.25.5 require ( - github.com/grafana/grafana-app-sdk v0.48.5 - github.com/grafana/grafana-app-sdk/logging v0.48.3 + github.com/grafana/grafana-app-sdk v0.48.7 + github.com/grafana/grafana-app-sdk/logging v0.48.7 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250915132226-585b53bc7dba - k8s.io/apimachinery v0.34.2 - k8s.io/apiserver v0.34.2 + k8s.io/apimachinery v0.34.3 + k8s.io/apiserver v0.34.3 k8s.io/klog/v2 v2.130.1 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -25,13 +25,23 @@ require ( github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect @@ -43,7 +53,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -57,42 +67,42 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/client-go v0.34.2 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/client-go v0.34.3 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/apps/shorturl/go.sum b/apps/shorturl/go.sum index 58ecc31fd82..36b5842b179 100644 --- a/apps/shorturl/go.sum +++ b/apps/shorturl/go.sum @@ -1,7 +1,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -25,14 +25,40 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= @@ -41,8 +67,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -56,10 +82,10 @@ github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGr github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250915132226-585b53bc7dba h1:Qam8QzVRsyZN39zgZ9Vj6e8PEfswvv2McnqCZ/v5NcI= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250915132226-585b53bc7dba/go.mod h1:rlJ/mmE0RQolOB2+HV3+bw+ZifHyPDQurBwZEus+Wm0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= @@ -83,8 +109,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -116,10 +142,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -134,30 +160,30 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -175,34 +201,34 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -211,41 +237,41 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0 gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= -k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/go.mod b/go.mod index 91d8a0a42fc..a7d3e52d9ea 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( github.com/blevesearch/bleve_index_api v1.2.7 // @grafana/grafana-search-and-storage github.com/blugelabs/bluge v0.2.2 // @grafana/grafana-backend-group github.com/blugelabs/bluge_segment_api v0.2.0 // @grafana/grafana-backend-group - github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // @grafana/grafana-backend-group + github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // @grafana/grafana-backend-group github.com/bwmarrin/snowflake v0.3.0 // @grafana/grafana-app-platform-squad github.com/centrifugal/centrifuge v0.38.0 // @grafana/grafana-app-platform-squad github.com/crewjam/saml v0.4.14 // @grafana/identity-access-team @@ -97,8 +97,8 @@ require ( github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f // @grafana/sharing-squad github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d // @grafana/grafana-operator-experience-squad github.com/grafana/grafana-api-golang-client v0.27.0 // @grafana/alerting-backend - github.com/grafana/grafana-app-sdk v0.48.5 // @grafana/grafana-app-platform-squad - github.com/grafana/grafana-app-sdk/logging v0.48.3 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana-app-sdk v0.48.7 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana-app-sdk/logging v0.48.7 // @grafana/grafana-app-platform-squad github.com/grafana/grafana-aws-sdk v1.3.0 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 // @grafana/partner-datasources github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 // @grafana/grafana-operator-experience-squad @@ -157,7 +157,7 @@ require ( github.com/prometheus/alertmanager v0.28.2 // @grafana/alerting-backend github.com/prometheus/client_golang v1.23.2 // @grafana/alerting-backend github.com/prometheus/client_model v0.6.2 // @grafana/grafana-backend-group - github.com/prometheus/common v0.67.3 // @grafana/alerting-backend + github.com/prometheus/common v0.67.4 // @grafana/alerting-backend github.com/prometheus/prometheus v0.303.1 // @grafana/alerting-backend github.com/prometheus/sigv4 v0.1.2 // @grafana/alerting-backend github.com/puzpuzpuz/xsync/v4 v4.2.0 // @grafana/grafana-backend-group @@ -166,7 +166,7 @@ require ( github.com/rs/cors v1.11.1 // @grafana/identity-access-team github.com/russellhaering/goxmldsig v1.4.0 // @grafana/grafana-backend-group github.com/shopspring/decimal v1.4.0 // @grafana/grafana-datasources-core-services - github.com/spf13/cobra v1.10.1 // @grafana/grafana-app-platform-squad + github.com/spf13/cobra v1.10.2 // @grafana/grafana-app-platform-squad github.com/spf13/pflag v1.0.10 // @grafana-app-platform-squad github.com/spyzhov/ajson v0.9.6 // @grafana/grafana-sharing-squad github.com/stretchr/testify v1.11.1 // @grafana/grafana-backend-group @@ -182,17 +182,17 @@ require ( github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // @grafana/grafana-operator-experience-squad github.com/yudai/gojsondiff v1.0.0 // @grafana/grafana-backend-group go.opentelemetry.io/collector/pdata v1.44.0 // @grafana/grafana-backend-group - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // @grafana/plugins-platform-backend + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // @grafana/plugins-platform-backend go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // @grafana/grafana-operator-experience-squad - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // @grafana/sharing-squad + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // @grafana/sharing-squad go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // @grafana/grafana-backend-group go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel v1.38.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel v1.39.0 // @grafana/grafana-backend-group go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/sdk v1.38.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/trace v1.38.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/sdk v1.39.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/trace v1.39.0 // @grafana/grafana-backend-group go.uber.org/atomic v1.11.0 // @grafana/alerting-backend go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage go.uber.org/mock v0.6.0 // @grafana/grafana-operator-experience-squad @@ -200,30 +200,30 @@ require ( go.yaml.in/yaml/v3 v3.0.4 // @grafana/alerting-backend gocloud.dev v0.43.0 // @grafana/grafana-app-platform-squad gocloud.dev/secrets/hashivault v0.43.0 // @grafana/grafana-operator-experience-squad - golang.org/x/crypto v0.45.0 // @grafana/grafana-backend-group - golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // @grafana/alerting-backend - golang.org/x/mod v0.30.0 // indirect; @grafana/grafana-backend-group - golang.org/x/net v0.47.0 // @grafana/oss-big-tent @grafana/partner-datasources - golang.org/x/oauth2 v0.33.0 // @grafana/identity-access-team - golang.org/x/sync v0.18.0 // @grafana/alerting-backend - golang.org/x/text v0.31.0 // @grafana/grafana-backend-group + golang.org/x/crypto v0.46.0 // @grafana/grafana-backend-group + golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // @grafana/alerting-backend + golang.org/x/mod v0.31.0 // indirect; @grafana/grafana-backend-group + golang.org/x/net v0.48.0 // @grafana/oss-big-tent @grafana/partner-datasources + golang.org/x/oauth2 v0.34.0 // @grafana/identity-access-team + golang.org/x/sync v0.19.0 // @grafana/alerting-backend + golang.org/x/text v0.32.0 // @grafana/grafana-backend-group golang.org/x/time v0.14.0 // @grafana/grafana-backend-group - golang.org/x/tools v0.39.0 // indirect; @grafana/grafana-as-code + golang.org/x/tools v0.40.0 // indirect; @grafana/grafana-as-code gonum.org/v1/gonum v0.16.0 // @grafana/oss-big-tent google.golang.org/api v0.242.0 // @grafana/grafana-backend-group google.golang.org/grpc v1.77.0 // @grafana/plugins-platform-backend - google.golang.org/protobuf v1.36.10 // @grafana/plugins-platform-backend + google.golang.org/protobuf v1.36.11 // @grafana/plugins-platform-backend gopkg.in/ini.v1 v1.67.0 // @grafana/alerting-backend gopkg.in/mail.v2 v2.3.1 // @grafana/grafana-backend-group - k8s.io/api v0.34.2 // @grafana/grafana-app-platform-squad - k8s.io/apimachinery v0.34.2 // @grafana/grafana-app-platform-squad - k8s.io/apiserver v0.34.2 // @grafana/grafana-app-platform-squad - k8s.io/client-go v0.34.2 // @grafana/grafana-app-platform-squad - k8s.io/component-base v0.34.2 // @grafana/grafana-app-platform-squad + k8s.io/api v0.34.3 // @grafana/grafana-app-platform-squad + k8s.io/apimachinery v0.34.3 // @grafana/grafana-app-platform-squad + k8s.io/apiserver v0.34.3 // @grafana/grafana-app-platform-squad + k8s.io/client-go v0.34.3 // @grafana/grafana-app-platform-squad + k8s.io/component-base v0.34.3 // @grafana/grafana-app-platform-squad k8s.io/klog/v2 v2.130.1 // @grafana/grafana-app-platform-squad k8s.io/kube-aggregator v0.34.2 // @grafana/grafana-app-platform-squad - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // @grafana/grafana-app-platform-squad - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // @grafana/partner-datasources + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // @grafana/grafana-app-platform-squad + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // @grafana/partner-datasources modernc.org/sqlite v1.40.1 // @grafana/grafana-backend-group pgregory.net/rapid v1.2.0 // @grafana/grafana-operator-experience-squad sigs.k8s.io/randfill v1.0.0 // @grafana/grafana-app-platform-squad @@ -295,7 +295,7 @@ replace ( ) require ( - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.1 // indirect cloud.google.com/go v0.121.4 // indirect cloud.google.com/go/auth v0.16.3 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect @@ -303,7 +303,7 @@ require ( cloud.google.com/go/iam v1.5.2 // indirect cloud.google.com/go/longrunning v0.6.7 // indirect cloud.google.com/go/monitoring v1.24.2 // indirect - cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 // indirect + cuelabs.dev/go/oci/ociregistry v0.0.0-20251212221603-3adeb8663819 // indirect github.com/Azure/azure-pipeline-go v0.2.3 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect @@ -397,7 +397,7 @@ require ( github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/coreos/go-systemd/v22 v22.6.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dennwc/varint v1.0.0 // indirect @@ -413,7 +413,7 @@ require ( github.com/dolthub/maphash v0.1.0 // indirect github.com/edsrzf/mmap-go v1.2.0 // indirect github.com/elazarl/goproxy v1.7.2 // indirect - github.com/emicklei/proto v1.13.2 // indirect + github.com/emicklei/proto v1.14.2 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/envoyproxy/go-control-plane/envoy v1.35.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect @@ -427,10 +427,10 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.24.0 // indirect github.com/go-openapi/errors v0.22.3 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect github.com/go-openapi/spec v0.22.0 // indirect - github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-openapi/swag v0.25.4 // indirect github.com/go-openapi/validate v0.25.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/goccy/go-json v0.10.5 // indirect @@ -445,7 +445,7 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/cel-go v0.26.1 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-github/v64 v64.0.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect @@ -501,7 +501,7 @@ require ( github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lestrrat-go/strftime v1.0.4 // indirect github.com/magefile/mage v1.15.0 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/matryer/is v1.4.1 // indirect github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect @@ -559,8 +559,8 @@ require ( github.com/pressly/goose/v3 v3.26.0 // indirect github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/exporter-toolkit v0.14.0 // indirect - github.com/prometheus/procfs v0.16.1 // indirect - github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d // indirect + github.com/prometheus/procfs v0.19.2 // indirect + github.com/protocolbuffers/txtpbfmt v0.0.0-20251124094003-fcb97cc64c7b // indirect github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect github.com/redis/rueidis v1.0.68 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect @@ -595,7 +595,7 @@ require ( github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect @@ -604,9 +604,9 @@ require ( github.com/zclconf/go-cty v1.16.3 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect go.etcd.io/bbolt v1.4.2 // indirect - go.etcd.io/etcd/api/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/v3 v3.6.4 // indirect + go.etcd.io/etcd/api/v3 v3.6.6 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.6 // indirect + go.etcd.io/etcd/client/v3 v3.6.6 // indirect go.mongodb.org/mongo-driver v1.17.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/collector/featuregate v1.44.0 // indirect @@ -618,29 +618,29 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.59.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect go.opentelemetry.io/otel/log v0.12.2 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/sdk/log v0.12.2 // indirect - go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect - golang.org/x/term v0.37.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc // indirect + golang.org/x/term v0.38.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto v0.0.0-20250715232539-7130f93afb79 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect @@ -648,13 +648,13 @@ require ( gopkg.in/telebot.v3 v3.3.8 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.34.2 // indirect - k8s.io/kms v0.34.2 // indirect + k8s.io/apiextensions-apiserver v0.34.3 // indirect + k8s.io/kms v0.34.3 // indirect modernc.org/libc v1.66.10 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) @@ -668,15 +668,17 @@ require ( github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/ebitengine/purego v0.8.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-openapi/swag/conv v0.25.1 // indirect - github.com/go-openapi/swag/fileutils v0.25.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect - github.com/go-openapi/swag/jsonutils v0.25.1 // indirect - github.com/go-openapi/swag/loading v0.25.1 // indirect - github.com/go-openapi/swag/mangling v0.25.1 // indirect - github.com/go-openapi/swag/stringutils v0.25.1 // indirect - github.com/go-openapi/swag/typeutils v0.25.1 // indirect - github.com/go-openapi/swag/yamlutils v0.25.1 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/magiconair/properties v1.8.10 // indirect github.com/moby/go-archive v0.1.0 // indirect diff --git a/go.sum b/go.sum index d056a11c1cb..426d6062d14 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.36.2-20250703125925-3f0fc buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.36.2-20250703125925-3f0fcf4bff96.1/go.mod h1:1M7nlq2ljfzb95x9LaA2j1gYIvDkVZii58mGvTa9ExM= c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0= c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w= -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= @@ -630,8 +630,8 @@ cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcP connectrpc.com/connect v1.18.1 h1:PAg7CjSAGvscaf6YZKUefjoih5Z/qYkyaTrBW8xvYPw= connectrpc.com/connect v1.18.1/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8= contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs= -cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 h1:R5wwEcbEZSBmeyg91MJZTxfd7WpBo2jPof3AYjRbxwY= -cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565/go.mod h1:5A4xfTzHTXfeVJBU6RAUf+QrlfTCW+017q/QiW+sMLg= +cuelabs.dev/go/oci/ociregistry v0.0.0-20251212221603-3adeb8663819 h1:Zh+Ur3OsoWpvALHPLT45nOekHkgOt+IOfutBbPqM17I= +cuelabs.dev/go/oci/ociregistry v0.0.0-20251212221603-3adeb8663819/go.mod h1:WjmQxb+W6nVNCgj8nXrF24lIz95AHwnSl36tpjDZSU8= cuelang.org/go v0.11.1 h1:pV+49MX1mmvDm8Qh3Za3M786cty8VKPWzQ1Ho4gZRP0= cuelang.org/go v0.11.1/go.mod h1:PBY6XvPUswPPJ2inpvUozP9mebDVTXaeehQikhZPBz0= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= @@ -982,8 +982,8 @@ github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvF github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= -github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -1064,8 +1064,9 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= @@ -1156,8 +1157,8 @@ github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVo github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY= -github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/emicklei/proto v1.14.2 h1:wJPxPy2Xifja9cEMrcA/g08art5+7CGJNFNk35iXC1I= +github.com/emicklei/proto v1.14.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -1287,16 +1288,16 @@ github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwds github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.20.1/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.20.3/go.mod h1:FviDZ46i9ivh810gqzFLl5NttD5q3tSlMLqLr6okedM= github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= @@ -1336,28 +1337,36 @@ github.com/go-openapi/swag v0.19.4/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.22.5/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= github.com/go-openapi/swag v0.22.6/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0= -github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs= -github.com/go-openapi/swag/fileutils v0.25.1 h1:rSRXapjQequt7kqalKXdcpIegIShhTPXx7yw0kek2uU= -github.com/go-openapi/swag/fileutils v0.25.1/go.mod h1:+NXtt5xNZZqmpIpjqcujqojGFek9/w55b3ecmOdtg8M= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= -github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8= -github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg= -github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw= -github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc= -github.com/go-openapi/swag/mangling v0.25.1 h1:XzILnLzhZPZNtmxKaz/2xIGPQsBsvmCjrJOWGNz/ync= -github.com/go-openapi/swag/mangling v0.25.1/go.mod h1:CdiMQ6pnfAgyQGSOIYnZkXvqhnnwOn997uXZMAd/7mQ= -github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw= -github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg= -github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA= -github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8= -github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk= -github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.22.4/go.mod h1:qm6O8ZIcPVdSY5219468Jv7kBdGvkiZLPOmqnqTUZ2A= @@ -1490,8 +1499,8 @@ github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PU github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -1635,10 +1644,10 @@ github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d h1:oXRJlb9UjVsl github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw= github.com/grafana/grafana-api-golang-client v0.27.0 h1:zIwMXcbCB4n588i3O2N6HfNcQogCNTd/vPkEXTr7zX8= github.com/grafana/grafana-api-golang-client v0.27.0/go.mod h1:uNLZEmgKtTjHBtCQMwNn3qsx2mpMb8zU+7T4Xv3NR9Y= -github.com/grafana/grafana-app-sdk v0.48.5 h1:MS8l9fTZz+VbTfgApn09jw27GxhQ6fNOWGhC4ydvZmM= -github.com/grafana/grafana-app-sdk v0.48.5/go.mod h1:HJsMOSBmt/D/Ihs1SvagOwmXKi0coBMVHlfvdd+qe9Y= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= +github.com/grafana/grafana-app-sdk v0.48.7/go.mod h1:DWsaaH39ZMHwSOSoUBaeW8paMrRaYsjRYlLwCJYd78k= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grafana/grafana-aws-sdk v1.3.0 h1:/bfJzP93rCel1GbWoRSq0oUo424MZXt8jAp2BK9w8tM= github.com/grafana/grafana-aws-sdk v1.3.0/go.mod h1:VGycF0JkCGKND2O5je1ucOqPJ0ZNhZYzV3c2bNBAaGk= github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 h1:FFcEA01tW+SmuJIuDbHOdgUBL+d7DPrZ2N4zwzPhfGk= @@ -1979,8 +1988,8 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= @@ -2299,8 +2308,8 @@ github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGy github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/common/assets v0.2.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= @@ -2321,15 +2330,15 @@ github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/prometheus/prometheus v0.0.0-20190818123050-43acd0e2e93f/go.mod h1:rMTlmxGCvukf2KMu3fClMDKLLoJ5hl61MhcJ7xKakf0= github.com/prometheus/prometheus v0.303.1 h1:He/2jRE6sB23Ew38AIoR1WRR3fCMgPlJA2E0obD2WSY= github.com/prometheus/prometheus v0.303.1/go.mod h1:WEq2ogBPZoLjj9x5K67VEk7ECR0nRD9XCjaOt1lsYck= github.com/prometheus/sigv4 v0.1.2 h1:R7570f8AoM5YnTUPFm3mjZH5q2k4D+I/phCWvZ4PXG8= github.com/prometheus/sigv4 v0.1.2/go.mod h1:GF9fwrvLgkQwDdQ5BXeV9XUSCH/IPNqzvAoaohfjqMU= -github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq7lB31IeJL8iy7jkUmU/PG1Sr8jVGhS749dbUA= -github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= +github.com/protocolbuffers/txtpbfmt v0.0.0-20251124094003-fcb97cc64c7b h1:fPVI9E6QNFYI0Ph3XpKUDrcAvbCifHvqYJcntFLPog8= +github.com/protocolbuffers/txtpbfmt v0.0.0-20251124094003-fcb97cc64c7b/go.mod h1:JSbkp0BviKovYYt9XunS95M3mLPibE9bGg+Y95DsEEY= github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU= github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0= @@ -2446,8 +2455,8 @@ github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155 github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -2546,8 +2555,8 @@ github.com/wk8/go-ordered-map v1.0.0 h1:BV7z+2PaK8LTSd/mWgY12HyMAo5CEgkHqbkVq2th github.com/wk8/go-ordered-map v1.0.0/go.mod h1:9ZIbRunKbuvfPKyBP1SIKLcXNlv74YCOZ3t3VTS6gRk= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= @@ -2601,15 +2610,15 @@ gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2 go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= -go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= -go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= +go.etcd.io/etcd/api/v3 v3.6.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ= +go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4= go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= -go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= +go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs= +go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI= go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= -go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= -go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= +go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec= +go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8= go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA= go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE= go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU= @@ -2648,14 +2657,14 @@ go.opentelemetry.io/contrib/detectors/gcp v1.38.0 h1:ZoYbqX7OaA/TAikspPl3ozPI6iY go.opentelemetry.io/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts= go.opentelemetry.io/contrib/exporters/autoexport v0.61.0 h1:XfzKtKSrbtYk9TNCF8dkO0Y9M7IOfb4idCwBOTwGBiI= go.opentelemetry.io/contrib/exporters/autoexport v0.61.0/go.mod h1:N6otC+qXTD5bAnbK2O1f/1SXq3cX+3KYSWrkBUqG0cw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0/go.mod h1:rsg1EO8LXSs2po50PB5CeY/MSVlhghuKBgXlKnqm6ks= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 h1:2pn7OzMewmYRiNtv1doZnLo3gONcnMHlFnmOR8Vgt+8= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0/go.mod h1:rjbQTDEPQymPE0YnRQp9/NuPwwtL0sesz/fnqRW/v84= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo= go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 h1:oPW/SRFyHgIgxrvNhSBzqvZER2N5kRlci3/rGTOuyWo= @@ -2663,8 +2672,8 @@ go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0/go.mod h1:B9Oka5QVD0bn go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= @@ -2675,12 +2684,12 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo= go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.12.2 h1:12vMqzLLNZtXuXbJhSENRg+Vvx+ynNilV8twBLBsXMY= @@ -2694,23 +2703,23 @@ go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCf go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.17.0/go.mod h1:U87sE0f5vQB7hwUoW98pW5Rz4ZDuCFBZFNUBlSgmDFQ= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0= go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -2788,8 +2797,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2805,8 +2814,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A= -golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -2852,8 +2861,8 @@ golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2939,8 +2948,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2978,8 +2987,8 @@ golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -3003,8 +3012,8 @@ golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -3138,11 +3147,11 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc h1:bH6xUXay0AIFMElXG2rQ4uiE+7ncwtiOdPfYK1NK2XA= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -3161,8 +3170,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -3185,8 +3194,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -3271,8 +3280,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk= golang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -3521,15 +3530,15 @@ google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go. google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -3600,8 +3609,8 @@ google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= @@ -3612,8 +3621,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= @@ -3663,19 +3672,19 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= k8s.io/api v0.0.0-20190813020757-36bff7324fb7/go.mod h1:3Iy+myeAORNCLgjd/Xu9ebwN7Vh59Bw0vh9jhoX+V58= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= +k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= k8s.io/apimachinery v0.0.0-20190809020650-423f5d784010/go.mod h1:Waf/xTS2FGRrgXCkO5FP3XxTOWh0qLf2QhL1qFZZ/R8= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= -k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= -k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= @@ -3683,17 +3692,17 @@ k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kms v0.34.2 h1:91rj4MDZLyIT9KxG8J5/CcMH666Z88CF/xJQeuPfJc8= -k8s.io/kms v0.34.2/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= +k8s.io/kms v0.34.3 h1:QzBOD0sk1bGQVMcZQAHGjtbP1iKZJUyhC6D0I+BTxIE= +k8s.io/kms v0.34.3/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= k8s.io/kube-aggregator v0.34.2 h1:Nn0Vksj67WHBL2x7bJ6vuxL44RbMTK6uRtXX+3vMVJk= k8s.io/kube-aggregator v0.34.2/go.mod h1:/tp4cc/1p2AvICsS4mjjSJakdrbhcGbRmj0mdHTdR2Q= k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= k8s.io/kube-openapi v0.0.0-20190722073852-5e22f3d471e6/go.mod h1:RZvgC8MSN6DjiMV6oIfEE9pDL9CYXokkfaCKZeHm3nc= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20190809000727-6c36bc71fc4a/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= @@ -3760,10 +3769,10 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= diff --git a/go.work.sum b/go.work.sum index 2e51fd8a06e..775e2633bc3 100644 --- a/go.work.sum +++ b/go.work.sum @@ -517,6 +517,7 @@ github.com/bsm/sarama-cluster v2.1.13+incompatible h1:bqU3gMJbWZVxLZ9PGWVKP05yOm github.com/bsm/sarama-cluster v2.1.13+incompatible/go.mod h1:r7ao+4tTNXvWm+VRpRJchr2kQhqxgmAp2iEX5W96gMM= github.com/bufbuild/protovalidate-go v0.2.1 h1:pJr07sYhliyfj/STAM7hU4J3FKpVeLVKvOBmOTN8j+s= github.com/bufbuild/protovalidate-go v0.2.1/go.mod h1:e7XXDtlxj5vlEyAgsrxpzayp4cEMKCSSb8ZCkin+MVA= +github.com/bufbuild/protovalidate-go v0.9.1 h1:cdrIA33994yCcJyEIZRL36ZGTe9UDM/WHs5MBHEimiE= github.com/bufbuild/protovalidate-go v0.9.1/go.mod h1:5jptBxfvlY51RhX32zR6875JfPBRXUsQjyZjm/NqkLQ= github.com/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/JMzngw= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= @@ -553,6 +554,7 @@ github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1Ig github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c h1:2zRrJWIt/f9c9HhNHAgrRgq0San5gRRUJTBXLkchal0= +github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= @@ -763,6 +765,7 @@ github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81 h1:6zl3BbBhdnMkpSj2YY30qV3gDcVBGtFgVsV3+/i+mKQ= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= @@ -799,6 +802,7 @@ github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFx github.com/gocraft/dbr/v2 v2.7.2 h1:ccUxMuz6RdZvD7VPhMRRMSS/ECF3gytPhPtcavjktHk= github.com/gocraft/dbr/v2 v2.7.2/go.mod h1:5bCqyIXO5fYn3jEp/L06QF4K1siFdhxChMjdNu6YJrg= github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= @@ -1884,6 +1888,7 @@ go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCg go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= @@ -2033,6 +2038,7 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/tools v0.24.1/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= @@ -2084,6 +2090,7 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go. google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822 h1:zWFRixYR5QlotL+Uv3YfsPRENIrQFXiGs+iwqel6fOQ= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= @@ -2115,8 +2122,11 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= @@ -2192,14 +2202,20 @@ k8s.io/code-generator v0.34.1 h1:WpphT26E+j7tEgIUfFr5WfbJrktCGzB3JoJH9149xYc= k8s.io/code-generator v0.34.1/go.mod h1:DeWjekbDnJWRwpw3s0Jat87c+e0TgkxoR4ar608yqvg= k8s.io/code-generator v0.34.2 h1:9bG6jTxmsU3HXE5BNYJTC8AZ1D6hVVfkm8yYSkdkGY0= k8s.io/code-generator v0.34.2/go.mod h1:dnDDEd6S/z4uZ+PG1aE58ySCi/lR4+qT3a4DddE4/2I= +k8s.io/code-generator v0.34.3 h1:6ipJKsJZZ9q21BO8I2jEj4OLN3y8/1n4aihKN0xKmQk= +k8s.io/code-generator v0.34.3/go.mod h1:oW73UPYpGLsbRN8Ozkhd6ZzkF8hzFCiYmvEuWZDroI4= k8s.io/component-base v0.26.2/go.mod h1:DxbuIe9M3IZPRxPIzhch2m1eT7uFrSBJUBuVCQEBivs= k8s.io/component-base v0.33.3/go.mod h1:ktBVsBzkI3imDuxYXmVxZ2zxJnYTZ4HAsVj9iF09qp4= k8s.io/cri-api v0.27.1/go.mod h1:+Ts/AVYbIo04S86XbTD73UPp/DkTiYxtsFeOFEu32L0= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6 h1:4s3/R4+OYYYUKptXPhZKjQ04WJ6EhQQVFdjOFvCazDk= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f h1:SLb+kxmzfA87x4E4brQzB33VBbT2+x7Zq9ROIHmGn9Q= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= +k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ= +k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index 96b596f71fb..f583105112e 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -9,19 +9,19 @@ require ( github.com/grafana/grafana/pkg/semconv v0.0.0-20250514132646-acbc7b54ed9e github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 github.com/stretchr/testify v1.11.1 - go.opentelemetry.io/otel v1.38.0 - k8s.io/api v0.34.2 - k8s.io/apimachinery v0.34.2 - k8s.io/apiserver v0.34.2 - k8s.io/client-go v0.34.2 - k8s.io/component-base v0.34.2 + go.opentelemetry.io/otel v1.39.0 + k8s.io/api v0.34.3 + k8s.io/apimachinery v0.34.3 + k8s.io/apiserver v0.34.3 + k8s.io/client-go v0.34.3 + k8s.io/component-base v0.34.3 k8s.io/klog/v2 v2.130.1 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e sigs.k8s.io/structured-merge-diff/v6 v6.3.1 ) require ( - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.1 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/apache/arrow-go/v18 v18.4.1 // indirect @@ -31,7 +31,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/coreos/go-systemd/v22 v22.6.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/fatih/color v1.18.0 // indirect @@ -40,10 +40,20 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -51,7 +61,7 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/cel-go v0.26.1 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect @@ -66,13 +76,11 @@ require ( github.com/hashicorp/yamux v0.1.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jaegertracing/jaeger-idl v0.5.0 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mailru/easyjson v0.9.0 // indirect github.com/mattetti/filebuffer v1.0.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -85,61 +93,60 @@ require ( github.com/onsi/ginkgo/v2 v2.22.2 // indirect github.com/onsi/gomega v1.36.2 // indirect github.com/pierrec/lz4/v4 v4.1.22 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stoewer/go-strcase v1.3.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect - go.etcd.io/etcd/api/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/v3 v3.6.4 // indirect + go.etcd.io/etcd/api/v3 v3.6.6 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.6 // indirect + go.etcd.io/etcd/client/v3 v3.6.6 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // indirect go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools v0.40.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index 246b05907cc..6def7534ecf 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= @@ -27,8 +27,8 @@ github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wX github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -57,19 +57,44 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -89,8 +114,8 @@ github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -133,8 +158,6 @@ github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5Xum github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ= @@ -153,8 +176,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 h1:hQWBtNqRYrI7CWIaUSXXtNKR90KzcUA5uiuxFVWw7sU= github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= @@ -195,8 +216,6 @@ github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -208,11 +227,11 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1: github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -223,8 +242,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -257,12 +276,12 @@ github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= -go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= -go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= -go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= -go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= -go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= -go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= +go.etcd.io/etcd/api/v3 v3.6.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ= +go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4= +go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs= +go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI= +go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec= +go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8= go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA= go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE= go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU= @@ -271,34 +290,34 @@ go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 h1:2pn7OzMewmYRiNtv1doZnLo3gONcnMHlFnmOR8Vgt+8= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0/go.mod h1:rjbQTDEPQymPE0YnRQp9/NuPwwtL0sesz/fnqRW/v84= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo= go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 h1:oPW/SRFyHgIgxrvNhSBzqvZER2N5kRlci3/rGTOuyWo= go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0/go.mod h1:B9Oka5QVD0bnmZNO6gBbBta6nohD/1Z+f9waH2oXyBs= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -314,15 +333,15 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A= -golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -330,18 +349,18 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -353,16 +372,16 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc h1:bH6xUXay0AIFMElXG2rQ4uiE+7ncwtiOdPfYK1NK2XA= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -370,8 +389,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -382,20 +401,20 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= @@ -404,26 +423,26 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= -k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= -k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 8261fc94967..4cc4a499bc0 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -8,28 +8,37 @@ require ( github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // @grafana/identity-access-team github.com/stretchr/testify v1.11.1 go.yaml.in/yaml/v3 v3.0.4 - k8s.io/apimachinery v0.34.2 - k8s.io/apiserver v0.34.2 - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 + k8s.io/apimachinery v0.34.3 + k8s.io/apiserver v0.34.3 + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 ) require ( + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect @@ -38,21 +47,22 @@ require ( github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/otel v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.31.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.32.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect ) diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 77c81a0b698..47519c2a832 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -1,3 +1,5 @@ +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -13,20 +15,46 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -38,8 +66,6 @@ github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGr github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -48,8 +74,6 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -79,16 +103,16 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -102,22 +126,22 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -128,12 +152,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -141,18 +165,18 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= -k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index feb659b9a0a..89edb050ed8 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -5,18 +5,18 @@ go 1.25.5 require ( github.com/google/go-cmp v0.7.0 github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 - github.com/grafana/grafana-app-sdk/logging v0.48.3 + github.com/grafana/grafana-app-sdk/logging v0.48.7 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 - go.opentelemetry.io/otel v1.38.0 - go.opentelemetry.io/otel/trace v1.38.0 - k8s.io/apimachinery v0.34.2 - k8s.io/apiserver v0.34.2 - k8s.io/component-base v0.34.2 + go.opentelemetry.io/otel v1.39.0 + go.opentelemetry.io/otel/trace v1.39.0 + k8s.io/apimachinery v0.34.3 + k8s.io/apiserver v0.34.3 + k8s.io/component-base v0.34.3 k8s.io/klog/v2 v2.130.1 - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 sigs.k8s.io/structured-merge-diff/v6 v6.3.1 ) @@ -26,7 +26,7 @@ require ( github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/coreos/go-systemd/v22 v22.6.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -34,14 +34,24 @@ require ( github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect @@ -50,60 +60,56 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/onsi/ginkgo/v2 v2.22.2 // indirect github.com/onsi/gomega v1.36.2 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect - go.etcd.io/etcd/api/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect - go.etcd.io/etcd/client/v3 v3.6.4 // indirect + go.etcd.io/etcd/api/v3 v3.6.6 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.6 // indirect + go.etcd.io/etcd/client/v3 v3.6.6 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.39.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + golang.org/x/tools v0.40.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.34.2 // indirect - k8s.io/client-go v0.34.2 // indirect - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/api v0.34.3 // indirect + k8s.io/client-go v0.34.3 // indirect + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 3eaa748643e..af28e90fadc 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -11,8 +11,8 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -32,17 +32,42 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= @@ -54,8 +79,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -71,8 +96,8 @@ github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGr github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= -github.com/grafana/grafana-app-sdk/logging v0.48.3 h1:72NUpGNiJXCNQz/on++YSsl38xuVYYBKv5kKQaOClX4= -github.com/grafana/grafana-app-sdk/logging v0.48.3/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= +github.com/grafana/grafana-app-sdk/logging v0.48.7 h1:Oa5qg473gka5+W/WQk61Xbw4YdAv+wV2Z4bJtzeCaQw= +github.com/grafana/grafana-app-sdk/logging v0.48.7/go.mod h1:5u3KalezoBAAo2Y3ytDYDAIIPvEqFLLDSxeiK99QxDU= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:IA4SOwun8QyST9c5UNs/fN37XL6boXXDvRYFcFwbipg= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o= @@ -85,8 +110,6 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLW github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -99,8 +122,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -129,11 +150,11 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1: github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -158,12 +179,12 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= -go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= -go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= -go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= -go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= -go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= -go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= +go.etcd.io/etcd/api/v3 v3.6.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ= +go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4= +go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs= +go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI= +go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec= +go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8= go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA= go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE= go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU= @@ -172,26 +193,26 @@ go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -207,8 +228,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -219,30 +240,30 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -250,8 +271,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -260,20 +281,20 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= @@ -281,26 +302,26 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= -k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= -k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= +k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= +k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.3 h1:uGH1qpDvSiYG4HVFqc6A3L4CKiX+aBWDrrsxHYK0Bdo= +k8s.io/apiserver v0.34.3/go.mod h1:QPnnahMO5C2m3lm6fPW3+JmyQbvHZQ8uudAu/493P2w= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= +k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= +k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/pkg/build/go.mod b/pkg/build/go.mod index 2bc514fcfd5..5170c87da05 100644 --- a/pkg/build/go.mod +++ b/pkg/build/go.mod @@ -10,14 +10,14 @@ replace github.com/docker/docker => github.com/moby/moby v28.0.1+incompatible require ( github.com/google/uuid v1.6.0 // indirect; @grafana/grafana-backend-group github.com/urfave/cli/v2 v2.27.7 // @grafana/grafana-backend-group - go.opentelemetry.io/otel v1.38.0 // indirect; @grafana/grafana-backend-group - go.opentelemetry.io/otel/sdk v1.38.0 // indirect; @grafana/grafana-backend-group - go.opentelemetry.io/otel/trace v1.38.0 // indirect; @grafana/grafana-backend-group - golang.org/x/net v0.47.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources - golang.org/x/sync v0.18.0 // @grafana/alerting-backend - golang.org/x/text v0.31.0 // indirect; @grafana/grafana-backend-group + go.opentelemetry.io/otel v1.39.0 // indirect; @grafana/grafana-backend-group + go.opentelemetry.io/otel/sdk v1.39.0 // indirect; @grafana/grafana-backend-group + go.opentelemetry.io/otel/trace v1.39.0 // indirect; @grafana/grafana-backend-group + golang.org/x/net v0.48.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources + golang.org/x/sync v0.19.0 // @grafana/alerting-backend + golang.org/x/text v0.32.0 // indirect; @grafana/grafana-backend-group google.golang.org/grpc v1.77.0 // indirect; @grafana/plugins-platform-backend - google.golang.org/protobuf v1.36.10 // indirect; @grafana/plugins-platform-backend + google.golang.org/protobuf v1.36.11 // indirect; @grafana/plugins-platform-backend ) require ( @@ -28,10 +28,10 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - golang.org/x/sys v0.38.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + golang.org/x/sys v0.39.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect ) require ( @@ -46,6 +46,7 @@ require ( github.com/Khan/genqlient v0.8.1 // indirect github.com/adrg/xdg v0.5.3 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect @@ -56,12 +57,12 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect go.opentelemetry.io/otel/log v0.12.2 // indirect go.opentelemetry.io/otel/sdk/log v0.12.2 // indirect - go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect ) diff --git a/pkg/build/go.sum b/pkg/build/go.sum index cdd5828ebd8..ae3e2125dc5 100644 --- a/pkg/build/go.sum +++ b/pkg/build/go.sum @@ -12,6 +12,8 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNg github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -61,8 +63,8 @@ github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGC github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 h1:tPLwQlXbJ8NSOfZc4OkgU5h2A38M4c9kfHSVc4PFQGs= @@ -71,48 +73,48 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0= go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/build/wire/go.mod b/pkg/build/wire/go.mod index b3ac434ea53..fc7aa317583 100644 --- a/pkg/build/wire/go.mod +++ b/pkg/build/wire/go.mod @@ -6,10 +6,10 @@ require ( github.com/google/go-cmp v0.7.0 github.com/google/subcommands v1.2.0 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 - golang.org/x/tools v0.39.0 + golang.org/x/tools v0.40.0 ) require ( - golang.org/x/mod v0.30.0 // indirect - golang.org/x/sync v0.18.0 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/sync v0.19.0 // indirect ) diff --git a/pkg/build/wire/go.sum b/pkg/build/wire/go.sum index bd76c016a3e..7091ed41da2 100644 --- a/pkg/build/wire/go.sum +++ b/pkg/build/wire/go.sum @@ -4,9 +4,9 @@ github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= diff --git a/pkg/codegen/go.mod b/pkg/codegen/go.mod index 7d01f823e78..bbf04b2e218 100644 --- a/pkg/codegen/go.mod +++ b/pkg/codegen/go.mod @@ -9,18 +9,18 @@ require ( github.com/grafana/cog v0.0.46 github.com/grafana/cuetsy v0.1.11 github.com/matryer/is v1.4.1 - golang.org/x/tools v0.39.0 + golang.org/x/tools v0.40.0 ) require ( github.com/cockroachdb/apd/v2 v2.0.2 // indirect github.com/dave/jennifer v1.7.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/proto v1.13.2 // indirect + github.com/emicklei/proto v1.14.2 // indirect github.com/expr-lang/expr v1.17.6 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect github.com/go-test/deep v1.1.1 // indirect github.com/golang/glog v1.2.5 // indirect github.com/google/go-cmp v0.7.0 // indirect @@ -29,9 +29,10 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.9 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect @@ -39,18 +40,19 @@ require ( github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d // indirect + github.com/protocolbuffers/txtpbfmt v0.0.0-20251124094003-fcb97cc64c7b // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/ugorji/go/codec v1.2.11 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/yalue/merged_fs v1.3.0 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/text v0.32.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/codegen/go.sum b/pkg/codegen/go.sum index 5e2bca5c947..12f05325b77 100644 --- a/pkg/codegen/go.sum +++ b/pkg/codegen/go.sum @@ -9,16 +9,18 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY= -github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/emicklei/proto v1.14.2 h1:wJPxPy2Xifja9cEMrcA/g08art5+7CGJNFNk35iXC1I= +github.com/emicklei/proto v1.14.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/expr-lang/expr v1.17.6 h1:1h6i8ONk9cexhDmowO/A64VPxHScu7qfSl2k8OlINec= github.com/expr-lang/expr v1.17.6/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -47,6 +49,7 @@ github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -57,8 +60,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -73,6 +76,7 @@ github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletI github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -81,6 +85,7 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b h1:zd/2RNzIRkoGGMjE+YIsZ85CnDIz672JK2F3Zl4vux4= github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b/go.mod h1:KjY0wibdYKc4DYkerHSbguaf3JeIPGhNJBp2BNiFH78= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= @@ -94,22 +99,22 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY= github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/plugins/codegen/go.mod b/pkg/plugins/codegen/go.mod index b4e296c934a..efbc0bdec2f 100644 --- a/pkg/plugins/codegen/go.mod +++ b/pkg/plugins/codegen/go.mod @@ -16,11 +16,11 @@ require ( cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/dave/dst v0.27.3 // indirect - github.com/emicklei/proto v1.13.2 // indirect + github.com/emicklei/proto v1.14.2 // indirect github.com/expr-lang/expr v1.17.6 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -28,7 +28,7 @@ require ( github.com/huandu/xstrings v1.5.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kr/text v0.2.0 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect @@ -37,17 +37,18 @@ require ( github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d // indirect + github.com/protocolbuffers/txtpbfmt v0.0.0-20251124094003-fcb97cc64c7b // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect - github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/woodsbury/decimal128 v1.4.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/yalue/merged_fs v1.3.0 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.47.0 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/text v0.31.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/text v0.32.0 // indirect + golang.org/x/tools v0.40.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pkg/plugins/codegen/go.sum b/pkg/plugins/codegen/go.sum index 635c691a7be..9d8aaf3e1b3 100644 --- a/pkg/plugins/codegen/go.sum +++ b/pkg/plugins/codegen/go.sum @@ -10,16 +10,18 @@ github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEy github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY= -github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/emicklei/proto v1.14.2 h1:wJPxPy2Xifja9cEMrcA/g08art5+7CGJNFNk35iXC1I= +github.com/emicklei/proto v1.14.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/expr-lang/expr v1.17.6 h1:1h6i8ONk9cexhDmowO/A64VPxHScu7qfSl2k8OlINec= github.com/expr-lang/expr v1.17.6/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= @@ -51,8 +53,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -76,8 +78,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq7lB31IeJL8iy7jkUmU/PG1Sr8jVGhS749dbUA= -github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= +github.com/protocolbuffers/txtpbfmt v0.0.0-20251124094003-fcb97cc64c7b h1:fPVI9E6QNFYI0Ph3XpKUDrcAvbCifHvqYJcntFLPog8= +github.com/protocolbuffers/txtpbfmt v0.0.0-20251124094003-fcb97cc64c7b/go.mod h1:JSbkp0BviKovYYt9XunS95M3mLPibE9bGg+Y95DsEEY= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= @@ -88,26 +90,28 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= -github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc= +github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY= github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index efbcd9222a6..ae8439e8663 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -8,13 +8,13 @@ require ( github.com/grafana/grafana/apps/scope v0.0.0-20251007093103-792853df9134 github.com/json-iterator/go v1.1.12 github.com/prometheus/client_golang v1.23.2 - github.com/prometheus/common v0.67.3 + github.com/prometheus/common v0.67.4 github.com/prometheus/prometheus v0.303.1 github.com/stretchr/testify v1.11.1 - go.opentelemetry.io/otel v1.38.0 - go.opentelemetry.io/otel/trace v1.38.0 - google.golang.org/protobuf v1.36.10 - k8s.io/apimachinery v0.34.2 + go.opentelemetry.io/otel v1.39.0 + go.opentelemetry.io/otel/trace v1.39.0 + google.golang.org/protobuf v1.36.11 + k8s.io/apimachinery v0.34.3 ) require ( @@ -36,16 +36,26 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.2 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/swag v0.25.4 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect @@ -61,11 +71,10 @@ require ( github.com/hashicorp/yamux v0.1.2 // indirect github.com/invopop/jsonschema v0.13.0 // indirect github.com/jaegertracing/jaeger-idl v0.5.0 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/mailru/easyjson v0.9.0 // indirect + github.com/mailru/easyjson v0.9.1 // indirect github.com/mattetti/filebuffer v1.0.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -80,47 +89,48 @@ require ( github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // indirect go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.39.0 // indirect + golang.org/x/tools v0.40.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/api v0.242.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect google.golang.org/grpc v1.77.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/client-go v0.34.2 // indirect + k8s.io/client-go v0.34.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect ) diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 9810bb19828..eec9ab3dffd 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -66,36 +66,46 @@ github.com/go-openapi/analysis v0.24.0 h1:vE/VFFkICKyYuTWYnplQ+aVr45vlG6NcZKC7Bd github.com/go-openapi/analysis v0.24.0/go.mod h1:GLyoJA+bvmGGaHgpfeDh8ldpGo69fAJg7eeMDMRCIrw= github.com/go-openapi/errors v0.22.3 h1:k6Hxa5Jg1TUyZnOwV2Lh81j8ayNw5VVYLvKrp4zFKFs= github.com/go-openapi/errors v0.22.3/go.mod h1:+WvbaBBULWCOna//9B9TbLNGSFOfF8lY9dw4hGiEiKQ= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= -github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= github.com/go-openapi/loads v0.23.1 h1:H8A0dX2KDHxDzc797h0+uiCZ5kwE2+VojaQVaTlXvS0= github.com/go-openapi/loads v0.23.1/go.mod h1:hZSXkyACCWzWPQqizAv/Ye0yhi2zzHwMmoXQ6YQml44= github.com/go-openapi/spec v0.22.0 h1:xT/EsX4frL3U09QviRIZXvkh80yibxQmtoEvyqug0Tw= github.com/go-openapi/spec v0.22.0/go.mod h1:K0FhKxkez8YNS94XzF8YKEMULbFrRw4m15i2YUht4L0= github.com/go-openapi/strfmt v0.24.0 h1:dDsopqbI3wrrlIzeXRbqMihRNnjzGC+ez4NQaAAJLuc= github.com/go-openapi/strfmt v0.24.0/go.mod h1:Lnn1Bk9rZjXxU9VMADbEEOo7D7CDyKGLsSKekhFr7s4= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0= -github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs= -github.com/go-openapi/swag/fileutils v0.25.1 h1:rSRXapjQequt7kqalKXdcpIegIShhTPXx7yw0kek2uU= -github.com/go-openapi/swag/fileutils v0.25.1/go.mod h1:+NXtt5xNZZqmpIpjqcujqojGFek9/w55b3ecmOdtg8M= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= -github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8= -github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo= -github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw= -github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc= -github.com/go-openapi/swag/mangling v0.25.1 h1:XzILnLzhZPZNtmxKaz/2xIGPQsBsvmCjrJOWGNz/ync= -github.com/go-openapi/swag/mangling v0.25.1/go.mod h1:CdiMQ6pnfAgyQGSOIYnZkXvqhnnwOn997uXZMAd/7mQ= -github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw= -github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg= -github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA= -github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8= -github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk= -github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-openapi/validate v0.25.0 h1:JD9eGX81hDTjoY3WOzh6WqxVBVl7xjsLnvDo1GL5WPU= github.com/go-openapi/validate v0.25.0/go.mod h1:SUY7vKrN5FiwK6LyvSwKjDfLNirSfWwHNgxd2l29Mmw= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= @@ -114,8 +124,8 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -162,8 +172,6 @@ github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5Xum github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -184,8 +192,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -239,10 +247,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.3 h1:shd26MlnwTw5jksTDhC7rTQIteBxy+ZZDr3t7F2xN2Q= -github.com/prometheus/common v0.67.3/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/prometheus/prometheus v0.303.1 h1:He/2jRE6sB23Ew38AIoR1WRR3fCMgPlJA2E0obD2WSY= github.com/prometheus/prometheus v0.303.1/go.mod h1:WEq2ogBPZoLjj9x5K67VEk7ECR0nRD9XCjaOt1lsYck= github.com/prometheus/sigv4 v0.1.2 h1:R7570f8AoM5YnTUPFm3mjZH5q2k4D+I/phCWvZ4PXG8= @@ -280,34 +288,34 @@ go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFX go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 h1:2pn7OzMewmYRiNtv1doZnLo3gONcnMHlFnmOR8Vgt+8= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0/go.mod h1:rjbQTDEPQymPE0YnRQp9/NuPwwtL0sesz/fnqRW/v84= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc= go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo= go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0 h1:oPW/SRFyHgIgxrvNhSBzqvZER2N5kRlci3/rGTOuyWo= go.opentelemetry.io/contrib/samplers/jaegerremote v0.32.0/go.mod h1:B9Oka5QVD0bnmZNO6gBbBta6nohD/1Z+f9waH2oXyBs= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -321,27 +329,27 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A= -golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -352,22 +360,22 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo= -golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc h1:bH6xUXay0AIFMElXG2rQ4uiE+7ncwtiOdPfYK1NK2XA= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -378,14 +386,14 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.242.0 h1:7Lnb1nfnpvbkCiZek6IXKdJ0MFuAZNAJKQfA1ws62xg= google.golang.org/api v0.242.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= +google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -396,18 +404,18 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= +k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= +k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= -k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= diff --git a/pkg/semconv/go.mod b/pkg/semconv/go.mod index 9998d716e35..30fc157eb75 100644 --- a/pkg/semconv/go.mod +++ b/pkg/semconv/go.mod @@ -2,9 +2,10 @@ module github.com/grafana/grafana/pkg/semconv go 1.25.5 -require go.opentelemetry.io/otel v1.38.0 +require go.opentelemetry.io/otel v1.39.0 require ( + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect ) diff --git a/pkg/semconv/go.sum b/pkg/semconv/go.sum index ee3256f1e7a..290db815506 100644 --- a/pkg/semconv/go.sum +++ b/pkg/semconv/go.sum @@ -1,3 +1,5 @@ +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -6,7 +8,7 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/tests/apis/config_test.go b/pkg/tests/apis/config_test.go index a9e8d87e0a0..665d79a5e50 100644 --- a/pkg/tests/apis/config_test.go +++ b/pkg/tests/apis/config_test.go @@ -51,7 +51,7 @@ const pluginsDiscoveryJSON = `[ "version": "" }, "scope": "Namespaced", - "singularResource": "plugins", + "singularResource": "plugin", "subresources": [ { "responseKind": { diff --git a/pkg/tests/apis/plugins/discovery_test.go b/pkg/tests/apis/plugins/discovery_test.go index 5e211cc45a1..1cbcfff487a 100644 --- a/pkg/tests/apis/plugins/discovery_test.go +++ b/pkg/tests/apis/plugins/discovery_test.go @@ -57,7 +57,7 @@ func TestIntegrationPluginsIntegrationDiscovery(t *testing.T) { "version": "" }, "scope": "Namespaced", - "singularResource": "plugins", + "singularResource": "plugin", "subresources": [ { "responseKind": { From 7620c1f24f157a6a0cd6172cb565936e02c2eb3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 06:19:03 +0000 Subject: [PATCH 174/297] deps(go): bump github.com/expr-lang/expr from 1.17.6 to 1.17.7 in /pkg/codegen (#115460) * deps(go): bump github.com/expr-lang/expr in /pkg/codegen Bumps [github.com/expr-lang/expr](https://github.com/expr-lang/expr) from 1.17.6 to 1.17.7. - [Release notes](https://github.com/expr-lang/expr/releases) - [Commits](https://github.com/expr-lang/expr/compare/v1.17.6...v1.17.7) --- updated-dependencies: - dependency-name: github.com/expr-lang/expr dependency-version: 1.17.7 dependency-type: indirect ... Signed-off-by: dependabot[bot] * use the same version everywhere --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan McKinley --- .citools/src/cog/go.mod | 2 +- pkg/codegen/go.mod | 2 +- pkg/codegen/go.sum | 4 ++-- pkg/plugins/codegen/go.mod | 2 +- pkg/plugins/codegen/go.sum | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.citools/src/cog/go.mod b/.citools/src/cog/go.mod index 83bc319bb5e..6a2eab53809 100644 --- a/.citools/src/cog/go.mod +++ b/.citools/src/cog/go.mod @@ -10,7 +10,7 @@ require ( github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/proto v1.13.2 // indirect - github.com/expr-lang/expr v1.17.0 // indirect + github.com/expr-lang/expr v1.17.7 // indirect github.com/getkin/kin-openapi v0.132.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect diff --git a/pkg/codegen/go.mod b/pkg/codegen/go.mod index bbf04b2e218..f608e1fb1c1 100644 --- a/pkg/codegen/go.mod +++ b/pkg/codegen/go.mod @@ -17,7 +17,7 @@ require ( github.com/dave/jennifer v1.7.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/proto v1.14.2 // indirect - github.com/expr-lang/expr v1.17.6 // indirect + github.com/expr-lang/expr v1.17.7 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-openapi/jsonpointer v0.22.4 // indirect github.com/go-openapi/swag/jsonname v0.25.4 // indirect diff --git a/pkg/codegen/go.sum b/pkg/codegen/go.sum index 12f05325b77..a5303120de5 100644 --- a/pkg/codegen/go.sum +++ b/pkg/codegen/go.sum @@ -11,8 +11,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/proto v1.14.2 h1:wJPxPy2Xifja9cEMrcA/g08art5+7CGJNFNk35iXC1I= github.com/emicklei/proto v1.14.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= -github.com/expr-lang/expr v1.17.6 h1:1h6i8ONk9cexhDmowO/A64VPxHScu7qfSl2k8OlINec= -github.com/expr-lang/expr v1.17.6/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= +github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8= +github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= diff --git a/pkg/plugins/codegen/go.mod b/pkg/plugins/codegen/go.mod index efbc0bdec2f..641cf9f9b49 100644 --- a/pkg/plugins/codegen/go.mod +++ b/pkg/plugins/codegen/go.mod @@ -17,7 +17,7 @@ require ( github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/dave/dst v0.27.3 // indirect github.com/emicklei/proto v1.14.2 // indirect - github.com/expr-lang/expr v1.17.6 // indirect + github.com/expr-lang/expr v1.17.7 // indirect github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-openapi/jsonpointer v0.22.4 // indirect github.com/go-openapi/swag/jsonname v0.25.4 // indirect diff --git a/pkg/plugins/codegen/go.sum b/pkg/plugins/codegen/go.sum index 9d8aaf3e1b3..a514789135c 100644 --- a/pkg/plugins/codegen/go.sum +++ b/pkg/plugins/codegen/go.sum @@ -12,8 +12,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/proto v1.14.2 h1:wJPxPy2Xifja9cEMrcA/g08art5+7CGJNFNk35iXC1I= github.com/emicklei/proto v1.14.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= -github.com/expr-lang/expr v1.17.6 h1:1h6i8ONk9cexhDmowO/A64VPxHScu7qfSl2k8OlINec= -github.com/expr-lang/expr v1.17.6/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= +github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8= +github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= From e224793ebafc241cb151b09ffec3344bef3d4ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 17 Dec 2025 07:24:25 +0100 Subject: [PATCH 175/297] Dashboard: Hide sidebar when playlist is playing (#115414) --- .../edit-pane/DashboardEditPaneSplitter.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx index 735edd1767f..fe6166062ad 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardEditPaneSplitter.tsx @@ -9,6 +9,7 @@ import { ElementSelectionContext, useSidebar, useStyles2, Sidebar } from '@grafa import NativeScrollbar, { DivScrollElement } from 'app/core/components/NativeScrollbar'; import { useGrafana } from 'app/core/context/GrafanaContext'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; +import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; import { KioskMode } from 'app/types/dashboard'; import { DashboardScene } from '../scene/DashboardScene'; @@ -32,7 +33,7 @@ export function DashboardEditPaneSplitter({ dashboard, isEditing, body, controls const styles = useStyles2(getStyles, headerHeight ?? 0); const { chrome } = useGrafana(); const { kioskMode } = chrome.useState(); - const isInKioskMode = kioskMode === KioskMode.Full; + const { isPlaying } = playlistSrv.useState(); if (!config.featureToggles.dashboardNewLayouts) { return ( @@ -94,8 +95,10 @@ export function DashboardEditPaneSplitter({ dashboard, isEditing, body, controls }; function renderBody() { + const renderWithoutSidebar = isPlaying || kioskMode === KioskMode.Full; + // In kiosk mode the full document body scrolls so we don't need to wrap in our own scrollbar - if (isInKioskMode) { + if (renderWithoutSidebar) { return (
Date: Wed, 17 Dec 2025 07:46:02 +0100 Subject: [PATCH 176/297] Chore: bump @grafana/plugin-e2e (#115466) --- package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 7a21333b222..1151e8ee235 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "@emotion/eslint-plugin": "11.12.0", "@grafana/eslint-config": "8.2.0", "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules", - "@grafana/plugin-e2e": "^3.0.3", + "@grafana/plugin-e2e": "^3.1.0", "@grafana/test-utils": "workspace:*", "@manypkg/get-packages": "^3.0.0", "@npmcli/package-json": "^6.0.0", diff --git a/yarn.lock b/yarn.lock index 7820f3c3c62..eaffc580fcc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3446,17 +3446,17 @@ __metadata: languageName: unknown linkType: soft -"@grafana/plugin-e2e@npm:^3.0.3": - version: 3.0.3 - resolution: "@grafana/plugin-e2e@npm:3.0.3" +"@grafana/plugin-e2e@npm:^3.1.0": + version: 3.1.0 + resolution: "@grafana/plugin-e2e@npm:3.1.0" dependencies: - "@grafana/e2e-selectors": "npm:^12.4.0-19890644192" + "@grafana/e2e-selectors": "npm:12.4.0-20165274911" semver: "npm:^7.5.4" uuid: "npm:^13.0.0" yaml: "npm:^2.3.4" peerDependencies: "@playwright/test": ^1.52.0 - checksum: 10/d9247d52cc5b65c91983212790610b0c2470d705fa4e37178d04cbf681c4fc80bb08b2c39ea6f0061cb0d78242f108a5c8e6fa1ad2f2209d6f15d34000cb1d00 + checksum: 10/a4003a1c594e8ecd771a8ab7af77357cfe2d942dee821d922e317da2eb7962f86c19bddcc4bdf4d0c793b3ebfec409095b25022b0e3664d66b93163e61cc3fb5 languageName: node linkType: hard @@ -19480,7 +19480,7 @@ __metadata: "@grafana/llm": "npm:1.0.1" "@grafana/monaco-logql": "npm:^0.0.8" "@grafana/o11y-ds-frontend": "workspace:*" - "@grafana/plugin-e2e": "npm:^3.0.3" + "@grafana/plugin-e2e": "npm:^3.1.0" "@grafana/plugin-ui": "npm:^0.11.1" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" From 8dd63baf50122ae50d7f04b3a44f0e6bcf346fe6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Dec 2025 07:08:40 +0000 Subject: [PATCH 177/297] deps(go): bump the k8s-io group across 2 directories with 2 updates (#115464) * deps(go): bump the k8s-io group across 2 directories with 2 updates Bumps the k8s-io group with 1 update in the / directory: [k8s.io/kube-aggregator](https://github.com/kubernetes/kube-aggregator). Bumps the k8s-io group with 1 update in the /hack directory: [k8s.io/code-generator](https://github.com/kubernetes/code-generator). Updates `k8s.io/kube-aggregator` from 0.34.2 to 0.34.3 - [Commits](https://github.com/kubernetes/kube-aggregator/compare/v0.34.2...v0.34.3) Updates `k8s.io/code-generator` from 0.34.2 to 0.34.3 - [Commits](https://github.com/kubernetes/code-generator/compare/v0.34.2...v0.34.3) --- updated-dependencies: - dependency-name: k8s.io/kube-aggregator dependency-version: 0.34.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io - dependency-name: k8s.io/code-generator dependency-version: 0.34.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: k8s-io ... Signed-off-by: dependabot[bot] * update all --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan McKinley --- apps/iam/go.sum | 4 ++-- apps/quotas/go.sum | 11 ----------- go.mod | 2 +- go.sum | 4 ++-- go.work.sum | 4 ---- hack/go.mod | 2 +- hack/go.sum | 4 ++-- hack/update-codegen.sh | 2 +- 8 files changed, 9 insertions(+), 24 deletions(-) diff --git a/apps/iam/go.sum b/apps/iam/go.sum index e9c440860de..4584bbd9cc1 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -2287,8 +2287,8 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kms v0.34.3 h1:QzBOD0sk1bGQVMcZQAHGjtbP1iKZJUyhC6D0I+BTxIE= k8s.io/kms v0.34.3/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= -k8s.io/kube-aggregator v0.34.2 h1:Nn0Vksj67WHBL2x7bJ6vuxL44RbMTK6uRtXX+3vMVJk= -k8s.io/kube-aggregator v0.34.2/go.mod h1:/tp4cc/1p2AvICsS4mjjSJakdrbhcGbRmj0mdHTdR2Q= +k8s.io/kube-aggregator v0.34.3 h1:rKsZWTD2As4dKuv+zzdJU0uo5H7bFlAEoSucai4mW6M= +k8s.io/kube-aggregator v0.34.3/go.mod h1:d4D8PV2FK4Qlq6u442FSum1tHPhK9tKdKBfH/A3R0I0= k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= diff --git a/apps/quotas/go.sum b/apps/quotas/go.sum index f331bdd9beb..6bbf64cb454 100644 --- a/apps/quotas/go.sum +++ b/apps/quotas/go.sum @@ -261,18 +261,7 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= -k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= -k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= -k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= -k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= -k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= -k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= -k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= -k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= diff --git a/go.mod b/go.mod index a7d3e52d9ea..fb1ab1ce189 100644 --- a/go.mod +++ b/go.mod @@ -221,7 +221,7 @@ require ( k8s.io/client-go v0.34.3 // @grafana/grafana-app-platform-squad k8s.io/component-base v0.34.3 // @grafana/grafana-app-platform-squad k8s.io/klog/v2 v2.130.1 // @grafana/grafana-app-platform-squad - k8s.io/kube-aggregator v0.34.2 // @grafana/grafana-app-platform-squad + k8s.io/kube-aggregator v0.34.3 // @grafana/grafana-app-platform-squad k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // @grafana/grafana-app-platform-squad k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // @grafana/partner-datasources modernc.org/sqlite v1.40.1 // @grafana/grafana-backend-group diff --git a/go.sum b/go.sum index 426d6062d14..c1a8d8ad808 100644 --- a/go.sum +++ b/go.sum @@ -3694,8 +3694,8 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kms v0.34.3 h1:QzBOD0sk1bGQVMcZQAHGjtbP1iKZJUyhC6D0I+BTxIE= k8s.io/kms v0.34.3/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= -k8s.io/kube-aggregator v0.34.2 h1:Nn0Vksj67WHBL2x7bJ6vuxL44RbMTK6uRtXX+3vMVJk= -k8s.io/kube-aggregator v0.34.2/go.mod h1:/tp4cc/1p2AvICsS4mjjSJakdrbhcGbRmj0mdHTdR2Q= +k8s.io/kube-aggregator v0.34.3 h1:rKsZWTD2As4dKuv+zzdJU0uo5H7bFlAEoSucai4mW6M= +k8s.io/kube-aggregator v0.34.3/go.mod h1:d4D8PV2FK4Qlq6u442FSum1tHPhK9tKdKBfH/A3R0I0= k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= k8s.io/kube-openapi v0.0.0-20190722073852-5e22f3d471e6/go.mod h1:RZvgC8MSN6DjiMV6oIfEE9pDL9CYXokkfaCKZeHm3nc= k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= diff --git a/go.work.sum b/go.work.sum index 775e2633bc3..1358a13e9f5 100644 --- a/go.work.sum +++ b/go.work.sum @@ -2198,10 +2198,6 @@ k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY= k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= -k8s.io/code-generator v0.34.1 h1:WpphT26E+j7tEgIUfFr5WfbJrktCGzB3JoJH9149xYc= -k8s.io/code-generator v0.34.1/go.mod h1:DeWjekbDnJWRwpw3s0Jat87c+e0TgkxoR4ar608yqvg= -k8s.io/code-generator v0.34.2 h1:9bG6jTxmsU3HXE5BNYJTC8AZ1D6hVVfkm8yYSkdkGY0= -k8s.io/code-generator v0.34.2/go.mod h1:dnDDEd6S/z4uZ+PG1aE58ySCi/lR4+qT3a4DddE4/2I= k8s.io/code-generator v0.34.3 h1:6ipJKsJZZ9q21BO8I2jEj4OLN3y8/1n4aihKN0xKmQk= k8s.io/code-generator v0.34.3/go.mod h1:oW73UPYpGLsbRN8Ozkhd6ZzkF8hzFCiYmvEuWZDroI4= k8s.io/component-base v0.26.2/go.mod h1:DxbuIe9M3IZPRxPIzhch2m1eT7uFrSBJUBuVCQEBivs= diff --git a/hack/go.mod b/hack/go.mod index e9ecfdc60b4..57179bc0550 100644 --- a/hack/go.mod +++ b/hack/go.mod @@ -2,7 +2,7 @@ module github.com/grafana/grafana/hack go 1.25.5 -require k8s.io/code-generator v0.34.2 +require k8s.io/code-generator v0.34.3 require ( github.com/go-logr/logr v1.4.2 // indirect diff --git a/hack/go.sum b/hack/go.sum index 25c035d777a..b427dbd73ef 100644 --- a/hack/go.sum +++ b/hack/go.sum @@ -12,8 +12,8 @@ golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= -k8s.io/code-generator v0.34.2 h1:9bG6jTxmsU3HXE5BNYJTC8AZ1D6hVVfkm8yYSkdkGY0= -k8s.io/code-generator v0.34.2/go.mod h1:dnDDEd6S/z4uZ+PG1aE58ySCi/lR4+qT3a4DddE4/2I= +k8s.io/code-generator v0.34.3 h1:6ipJKsJZZ9q21BO8I2jEj4OLN3y8/1n4aihKN0xKmQk= +k8s.io/code-generator v0.34.3/go.mod h1:oW73UPYpGLsbRN8Ozkhd6ZzkF8hzFCiYmvEuWZDroI4= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f h1:SLb+kxmzfA87x4E4brQzB33VBbT2+x7Zq9ROIHmGn9Q= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 59976c9bf3c..03dd95943a2 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -11,7 +11,7 @@ set -o pipefail SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. pushd "${SCRIPT_ROOT}/hack" && GO111MODULE=on go mod tidy && popd -CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo $(go env GOPATH)/pkg/mod/k8s.io/code-generator@v0.34.2)} +CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo $(go env GOPATH)/pkg/mod/k8s.io/code-generator@v0.34.3)} OUTDIR="${HOME}/go/src" OPENAPI_VIOLATION_EXCEPTIONS_FILENAME="zz_generated.openapi_violation_exceptions.list" From 931806d1e9cd57a6bbb164bbc78cc7a06958b6c2 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 17 Dec 2025 08:10:26 +0100 Subject: [PATCH 178/297] NPM: Dispatch to plugin-tools on e2e-selectors changes (#115218) --- .github/workflows/release-npm.yml | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/.github/workflows/release-npm.yml b/.github/workflows/release-npm.yml index 999ccbc5e4a..9c332d0675a 100644 --- a/.github/workflows/release-npm.yml +++ b/.github/workflows/release-npm.yml @@ -90,6 +90,7 @@ jobs: with: persist-credentials: false ref: ${{ inputs.grafana_commit }} + fetch-depth: 2 # Need HEAD~1 for e2e-selectors change detection - name: Setup Node uses: ./.github/actions/setup-node @@ -127,3 +128,43 @@ jobs: env: NPM_TAG: ${{ steps.npm-tag.outputs.NPM_TAG }} run: ./scripts/publish-npm-packages.sh --dist-tag "$NPM_TAG" --registry 'https://registry.npmjs.org/' + + # Notify plugin-tools when e2e-selectors changes so it can update its bundled version + - name: Check for e2e-selectors changes + id: check-e2e-changes + run: | + CHANGES=$(git diff --name-only HEAD~1 HEAD -- packages/grafana-e2e-selectors | wc -l) + echo "changes=$CHANGES" >> "$GITHUB_OUTPUT" + if [ "$CHANGES" -gt 0 ]; then + echo "Detected $CHANGES file(s) changed in packages/grafana-e2e-selectors" + fi + + - name: Get Vault secrets + if: steps.check-e2e-changes.outputs.changes > 0 + id: vault-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@main + with: + repo_secrets: | + GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY + + - name: Generate token for plugin-tools + if: steps.check-e2e-changes.outputs.changes > 0 + id: generate_token + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a + with: + app_id: ${{ vars.DELIVERY_BOT_APP_ID }} + private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }} + repositories: '["plugin-tools"]' + permissions: '{"actions": "write"}' + + - name: Dispatch to plugin-tools + if: steps.check-e2e-changes.outputs.changes > 0 + env: + VERSION: ${{ inputs.version }} + GH_TOKEN: ${{ steps.generate_token.outputs.token }} + run: | + echo "Dispatching bump-e2e-selectors workflow to grafana/plugin-tools with version $VERSION" + gh workflow run bump-e2e-selectors.yml \ + --repo grafana/plugin-tools \ + --ref main \ + --field version="$VERSION" From ee483a816ca6d53b15a0c62908e6da366ba97419 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 17 Dec 2025 08:20:46 +0100 Subject: [PATCH 179/297] E2E Selectors: Fix test title typo (#115418) fix typo --- packages/grafana-e2e-selectors/src/resolver.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-e2e-selectors/src/resolver.test.ts b/packages/grafana-e2e-selectors/src/resolver.test.ts index a306f9261ed..888cf4fdc54 100644 --- a/packages/grafana-e2e-selectors/src/resolver.test.ts +++ b/packages/grafana-e2e-selectors/src/resolver.test.ts @@ -51,7 +51,7 @@ describe('Resolver', () => { expect(pages.Alerting.AddAlertRule.url).toBe('/alerting/new'); }); - it('should throw error if an invalid semver range is used in versioned selector', () => { + it('should throw an error if an invalid semver range is used in versioned selector', () => { expect(() => resolveSelectors({ Alerting: { From 00ea4024a810b50694b77a4c164b737f536fcb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 17 Dec 2025 08:36:34 +0100 Subject: [PATCH 180/297] ControlsMenu: Fix button spacing (#115468) --- .../dashboard-scene/scene/DashboardDataLayerControls.tsx | 6 ++++-- .../dashboard-scene/scene/DashboardLinksControls.tsx | 3 ++- .../dashboard-controls-menu/DashboardControlsMenuButton.tsx | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/DashboardDataLayerControls.tsx b/public/app/features/dashboard-scene/scene/DashboardDataLayerControls.tsx index 24f7e2dbd79..9a06277da86 100644 --- a/public/app/features/dashboard-scene/scene/DashboardDataLayerControls.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardDataLayerControls.tsx @@ -15,13 +15,14 @@ export function DashboardDataLayerControls({ dashboard }: { dashboard: Dashboard const state = sceneGraph.getData(dashboard).useState(); // It is possible to render the controls for the annotation data layers in separate places using the `placement` property. // In case it's not specified, we are rendering the controls here (default). - const isDefaultPlacement = (layer: SceneDataLayerProvider) => layer.state.placement === undefined; + const isDefaultPlacementAndNotHidden = (layer: SceneDataLayerProvider) => + layer.state.placement === undefined && !layer.state.isHidden; const styles = useStyles2(getStyles); if (isDashboardDataLayerSetState(state)) { return ( <> - {state.annotationLayers.filter(isDefaultPlacement).map((layer) => ( + {state.annotationLayers.filter(isDefaultPlacementAndNotHidden).map((layer) => (
@@ -35,6 +36,7 @@ export function DashboardDataLayerControls({ dashboard }: { dashboard: Dashboard const getStyles = (theme: GrafanaTheme2) => ({ container: css({ + label: 'dashboard-data-layer-controls', display: 'inline-flex', alignItems: 'center', verticalAlign: 'middle', diff --git a/public/app/features/dashboard-scene/scene/DashboardLinksControls.tsx b/public/app/features/dashboard-scene/scene/DashboardLinksControls.tsx index d95c54440c9..6a9ad667217 100644 --- a/public/app/features/dashboard-scene/scene/DashboardLinksControls.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardLinksControls.tsx @@ -18,7 +18,7 @@ export function DashboardLinksControls({ links, dashboard }: Props) { const uid = dashboard.state.uid; const styles = useStyles2(getStyles); - if (!links || !uid) { + if (!links || !uid || links.length === 0) { return null; } @@ -36,6 +36,7 @@ export function DashboardLinksControls({ links, dashboard }: Props) { function getStyles(theme: GrafanaTheme2) { return { linksContainer: css({ + label: 'dashboard-links-controls', display: 'inline-flex', gap: theme.spacing(1), marginRight: theme.spacing(1), diff --git a/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenuButton.tsx b/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenuButton.tsx index 898eb471373..f3a98f2e9b0 100644 --- a/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenuButton.tsx +++ b/public/app/features/dashboard-scene/scene/dashboard-controls-menu/DashboardControlsMenuButton.tsx @@ -47,5 +47,7 @@ export function DashboardControlsButton({ dashboard }: { dashboard: DashboardSce const getStyles = (theme: GrafanaTheme2) => ({ dropdownButton: css({ display: 'inline-flex', + marginBottom: theme.spacing(1), + marginRight: theme.spacing(1), }), }); From aa3b9dc4da935d5a9b60ec08bfb41aa6a1f393cc Mon Sep 17 00:00:00 2001 From: Rafael Bortolon Paulovic Date: Wed, 17 Dec 2025 10:09:57 +0100 Subject: [PATCH 181/297] Unified: Run resource data migrations at startup (#114857) * chore: uncomment unified migration * chore: adapt and fix tests * chore: dynamically bump max conns if needed during migration * chore: copilot suggestions * chore: pass ctx in RegisterMigration * chore: make playlists opt-out and dashboards opt-in * chore: adjust dashboard test * chore: disable enable log in test * chore: address review comments - do not use pointer config - add migration registry * chore: more consistent naming * chore: fix playlist discovery test --- pkg/infra/metrics/metrics.go | 2 +- .../apis/dashboard/legacy/sql_dashboards.go | 6 - pkg/setting/setting.go | 3 + pkg/setting/setting_unified_storage.go | 43 +++- pkg/setting/setting_unified_storage_test.go | 66 ++++-- .../unified/migrations/migrator_test.go | 123 +++++++++++- pkg/storage/unified/migrations/resources.go | 59 +++++- .../unified/migrations/resources_test.go | 92 +++++++++ pkg/storage/unified/migrations/service.go | 33 ++- .../alerting/api_convert_prometheus_test.go | 11 +- .../api/alerting/api_provisioning_test.go | 9 +- pkg/tests/api/alerting/api_ruler_test.go | 27 +-- pkg/tests/api/annotations/annotations_test.go | 5 +- .../api/dashboards/api_dashboards_test.go | 95 ++++++--- pkg/tests/apis/collections/stars_test.go | 7 +- pkg/tests/apis/dashboard/dashboards_test.go | 14 +- .../integration/api_validation_test.go | 12 +- .../library_panels_api_validation_test.go | 9 +- pkg/tests/apis/dashboard/search_test.go | 7 +- pkg/tests/apis/folder/folder_tree_test.go | 7 +- pkg/tests/apis/folder/folders_test.go | 121 ++++++----- .../playlist.grafana.app-v0alpha1.json | 28 ++- pkg/tests/apis/playlist/playlist_test.go | 190 ++++++++++-------- pkg/tests/testinfra/testinfra.go | 10 + 24 files changed, 705 insertions(+), 274 deletions(-) create mode 100644 pkg/storage/unified/migrations/resources_test.go diff --git a/pkg/infra/metrics/metrics.go b/pkg/infra/metrics/metrics.go index c1995545df8..19113ed3b1f 100644 --- a/pkg/infra/metrics/metrics.go +++ b/pkg/infra/metrics/metrics.go @@ -224,7 +224,7 @@ var ( MStatTotalRepositories prometheus.Gauge // MUnifiedStorageMigrationStatus indicates the migration status for unified storage in this instance. - // Possible values: 0 (default/undefined), 1 (migration disabled), 2 (migration would run). + // Possible values: 0 (default/undefined), 1 (migration disabled), 2 (migration would run), 3 (migration will run). MUnifiedStorageMigrationStatus prometheus.Gauge ) diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index 077cf0a6700..7df610f375e 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -154,12 +154,6 @@ func (a *dashboardSqlAccess) executeQuery(ctx context.Context, helper *legacysql return nil }) - // Use transaction from unified storage if available in the context. - // This allows us to run migrations in a transaction which is specifically required for SQLite. - if tx == nil { - tx = resource.TransactionFromContext(ctx) - } - if tx != nil { return tx.QueryContext(ctx, query, args...) } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 8e309433032..1af02aa0d3a 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -632,6 +632,9 @@ type UnifiedStorageConfig struct { DataSyncerInterval time.Duration // DataSyncerRecordsLimit defines how many records will be processed at max during a sync invocation. DataSyncerRecordsLimit int + // EnableMigration indicates whether migration is enabled for the resource. + // If not set, will use the default from MigratedUnifiedResources. + EnableMigration bool } type InstallPlugin struct { diff --git a/pkg/setting/setting_unified_storage.go b/pkg/setting/setting_unified_storage.go index 72e01ce6ce9..8086151c40a 100644 --- a/pkg/setting/setting_unified_storage.go +++ b/pkg/setting/setting_unified_storage.go @@ -8,10 +8,17 @@ import ( "github.com/grafana/grafana/pkg/util/osutil" ) -var MigratedUnifiedResources = []string{ - "playlists.playlist.grafana.app", - "folders.folder.grafana.app", - "dashboards.dashboard.grafana.app", +const ( + PlaylistResource = "playlists.playlist.grafana.app" + FolderResource = "folders.folder.grafana.app" + DashboardResource = "dashboards.dashboard.grafana.app" +) + +// MigratedUnifiedResources maps resources to a boolean indicating if migration is enabled by default +var MigratedUnifiedResources = map[string]bool{ + PlaylistResource: true, // enabled by default + FolderResource: false, + DashboardResource: false, } // read storage configs from ini file. They look like: @@ -46,12 +53,19 @@ func (cfg *Cfg) setUnifiedStorageConfig() { // parse dataSyncerInterval from resource section dataSyncerInterval := section.Key("dataSyncerInterval").MustDuration(time.Hour) + // parse EnableMigration from resource section + enableMigration := MigratedUnifiedResources[resourceName] + if section.HasKey("enableMigration") { + enableMigration = section.Key("enableMigration").MustBool(MigratedUnifiedResources[resourceName]) + } + storageConfig[resourceName] = UnifiedStorageConfig{ DualWriterMode: rest.DualWriterMode(dualWriterMode), DualWriterPeriodicDataSyncJobEnabled: dualWriterPeriodicDataSyncJobEnabled, DualWriterMigrationDataSyncDisabled: dualWriterMigrationDataSyncDisabled, DataSyncerRecordsLimit: dataSyncerRecordsLimit, DataSyncerInterval: dataSyncerInterval, + EnableMigration: enableMigration, } } cfg.UnifiedStorage = storageConfig @@ -61,8 +75,8 @@ func (cfg *Cfg) setUnifiedStorageConfig() { cfg.DisableDataMigrations = section.Key("disable_data_migrations").MustBool(false) if !cfg.DisableDataMigrations && cfg.getUnifiedStorageType() == "unified" { // Helper log to find instances running migrations in the future - cfg.Logger.Info("Unified migration configs not yet enforced") - // cfg.enforceMigrationToUnifiedConfigs() // TODO: uncomment when ready for release + cfg.Logger.Info("Unified migration configs enforced") + cfg.enforceMigrationToUnifiedConfigs() } else { // Helper log to find instances disabling migration cfg.Logger.Info("Unified migration configs enforcement disabled", "storage_type", cfg.getUnifiedStorageType(), "disable_data_migrations", cfg.DisableDataMigrations) @@ -107,7 +121,6 @@ func (cfg *Cfg) setUnifiedStorageConfig() { cfg.MinFileIndexBuildVersion = section.Key("min_file_index_build_version").MustString("") } -// nolint:unused // enforceMigrationToUnifiedConfigs enforces configurations required to run migrated resources in mode 5 // All migrated resources in MigratedUnifiedResources are set to mode 5 and unified search is enabled func (cfg *Cfg) enforceMigrationToUnifiedConfigs() { @@ -118,14 +131,22 @@ func (cfg *Cfg) enforceMigrationToUnifiedConfigs() { section.Key("enable_search").SetValue("true") cfg.EnableSearch = true } - for _, resource := range MigratedUnifiedResources { - cfg.Logger.Info("Enforcing mode 5 for resource in unified storage", "resource", resource) - if oldCfg, ok := cfg.UnifiedStorage[resource]; ok { - cfg.Logger.Info("Overriding unified storage config for migrated resource", "resource", resource, "old_config", oldCfg) + for resource, enabledByDefault := range MigratedUnifiedResources { + resourceCfg, ok := cfg.UnifiedStorage[resource] + if ok { + if !resourceCfg.EnableMigration { + cfg.Logger.Info("Resource migration disabled", "resource", resource) + continue + } + cfg.Logger.Info("Overriding unified storage config for migrated resource", "resource", resource, "old_config", resourceCfg) + } else if !enabledByDefault { + continue } + cfg.Logger.Info("Enforcing mode 5 for resource in unified storage", "resource", resource) cfg.UnifiedStorage[resource] = UnifiedStorageConfig{ DualWriterMode: 5, DualWriterMigrationDataSyncDisabled: true, + EnableMigration: true, } } } diff --git a/pkg/setting/setting_unified_storage_test.go b/pkg/setting/setting_unified_storage_test.go index 094033a4244..c112dc85962 100644 --- a/pkg/setting/setting_unified_storage_test.go +++ b/pkg/setting/setting_unified_storage_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/stretchr/testify/assert" ) @@ -13,31 +14,56 @@ func TestCfg_setUnifiedStorageConfig(t *testing.T) { err := cfg.Load(CommandLineArgs{HomePath: "../../", Config: "../../conf/defaults.ini"}) assert.NoError(t, err) - s, err := cfg.Raw.NewSection("unified_storage.playlists.playlist.grafana.app") - assert.NoError(t, err) + setSectionKey := func(sectionName, key, value string) { + section := cfg.Raw.Section(sectionName) // Gets existing or creates new + _, err := section.NewKey(key, value) + assert.NoError(t, err) + } - _, err = s.NewKey("dualWriterMode", "2") - assert.NoError(t, err) + setMigratedResourceKey := func(key, value string) { + for migratedResource := range MigratedUnifiedResources { + setSectionKey("unified_storage."+migratedResource, key, value) + } + } - _, err = s.NewKey("dualWriterPeriodicDataSyncJobEnabled", "true") - assert.NoError(t, err) + validateMigratedResources := func(optIn bool) { + for migratedResource, enabled := range MigratedUnifiedResources { + resourceCfg, exists := cfg.UnifiedStorage[migratedResource] - _, err = s.NewKey("dataSyncerRecordsLimit", "1001") - assert.NoError(t, err) + isEnabled := enabled + if optIn { + isEnabled = true + } - _, err = s.NewKey("dataSyncerInterval", "10m") - assert.NoError(t, err) + if !isEnabled { + if exists { + assert.Equal(t, rest.DualWriterMode(1), resourceCfg.DualWriterMode, migratedResource) + } + continue + } + assert.Equal(t, exists, true, migratedResource) + + assert.Equal(t, UnifiedStorageConfig{ + DualWriterMode: 5, + DualWriterMigrationDataSyncDisabled: true, + EnableMigration: isEnabled, + }, resourceCfg, migratedResource) + } + } + + setMigratedResourceKey("dualWriterMode", "1") // migrated resources enabled by default will change to 5 in setUnifiedStorageConfig + + setSectionKey("unified_storage.resource.not_migrated.grafana.app", "dualWriterMode", "2") + setSectionKey("unified_storage.resource.not_migrated.grafana.app", "dualWriterPeriodicDataSyncJobEnabled", "true") + setSectionKey("unified_storage.resource.not_migrated.grafana.app", "dataSyncerRecordsLimit", "1001") + setSectionKey("unified_storage.resource.not_migrated.grafana.app", "dataSyncerInterval", "10m") // Add unified_storage section for index settings - unifiedStorageSection, err := cfg.Raw.NewSection("unified_storage") - assert.NoError(t, err) - - _, err = unifiedStorageSection.NewKey("index_min_count", "5") - assert.NoError(t, err) + setSectionKey("unified_storage", "index_min_count", "5") cfg.setUnifiedStorageConfig() - value, exists := cfg.UnifiedStorage["playlists.playlist.grafana.app"] + value, exists := cfg.UnifiedStorage["resource.not_migrated.grafana.app"] assert.Equal(t, exists, true) assert.Equal(t, value, UnifiedStorageConfig{ @@ -47,6 +73,14 @@ func TestCfg_setUnifiedStorageConfig(t *testing.T) { DataSyncerInterval: time.Minute * 10, }) + validateMigratedResources(false) + + setMigratedResourceKey("enableMigration", "true") // will be changed to 5 in setUnifiedStorageConfig + + cfg.setUnifiedStorageConfig() + + validateMigratedResources(true) + // Test that index settings are correctly parsed assert.Equal(t, 5, cfg.IndexMinCount) }) diff --git a/pkg/storage/unified/migrations/migrator_test.go b/pkg/storage/unified/migrations/migrator_test.go index 92afe698a98..8552092d066 100644 --- a/pkg/storage/unified/migrations/migrator_test.go +++ b/pkg/storage/unified/migrations/migrator_test.go @@ -162,14 +162,49 @@ func runMigrationTestSuite(t *testing.T, testCases []resourceMigratorTestCase) { } }) - t.Run("Step 3: verify data is migrated to unified storage", func(t *testing.T) { - // Migrations will run automatically at startup and mode 5 is enforced by the config + t.Run("Step 3: verify that opted-out resources are not migrated", func(t *testing.T) { + // Build unified storage config for Mode5 + unifiedConfig := make(map[string]setting.UnifiedStorageConfig) + for _, tc := range testCases { + for _, gvr := range tc.resources() { + resourceKey := fmt.Sprintf("%s.%s", gvr.Resource, gvr.Group) + unifiedConfig[resourceKey] = setting.UnifiedStorageConfig{ + DualWriterMode: grafanarest.Mode5, + EnableMigration: false, + } + } + } + + helper := apis.NewK8sTestHelperWithOpts(t, apis.K8sTestHelperOpts{ + GrafanaOpts: testinfra.GrafanaOpts{ + AppModeProduction: true, + DisableAnonymous: true, + DisableDBCleanup: true, + APIServerStorageType: "unified", + UnifiedStorageConfig: unifiedConfig, + }, + Org1Users: org1, + OrgBUsers: orgB, + }) + t.Cleanup(helper.Shutdown) + + for _, state := range testStates { + t.Run(state.tc.name(), func(t *testing.T) { + // Verify resources don't exist in unified storage yet + state.tc.verify(t, helper, false) + }) + } + verifyRegisteredMigrations(t, helper, false, true) + }) + + t.Run("Step 4: verify data is migrated to unified storage", func(t *testing.T) { + // Migrations enabled by default will run automatically at startup and mode 5 is enforced by the config helper := apis.NewK8sTestHelperWithOpts(t, apis.K8sTestHelperOpts{ GrafanaOpts: testinfra.GrafanaOpts{ - // EnableLog: true, AppModeProduction: true, DisableAnonymous: true, DisableDataMigrations: false, // Run migrations at startup + DisableDBCleanup: true, APIServerStorageType: "unified", }, Org1Users: org1, @@ -183,7 +218,89 @@ func runMigrationTestSuite(t *testing.T, testCases []resourceMigratorTestCase) { state.tc.verify(t, helper, true) }) } + + t.Logf("Verifying migrations are correctly registered") + verifyRegisteredMigrations(t, helper, true, false) }) + + t.Run("Step 5: verify data is migrated for all migrations", func(t *testing.T) { + // Trigger migrations that are not enabled by default + unifiedConfig := make(map[string]setting.UnifiedStorageConfig) + for _, tc := range testCases { + for _, gvr := range tc.resources() { + resourceKey := fmt.Sprintf("%s.%s", gvr.Resource, gvr.Group) + unifiedConfig[resourceKey] = setting.UnifiedStorageConfig{ + EnableMigration: true, + } + } + } + helper := apis.NewK8sTestHelperWithOpts(t, apis.K8sTestHelperOpts{ + GrafanaOpts: testinfra.GrafanaOpts{ + // EnableLog: true, + AppModeProduction: true, + DisableAnonymous: true, + DisableDataMigrations: false, + APIServerStorageType: "unified", + UnifiedStorageConfig: unifiedConfig, + }, + Org1Users: org1, + OrgBUsers: orgB, + }) + t.Cleanup(helper.Shutdown) + + for _, state := range testStates { + t.Run(state.tc.name(), func(t *testing.T) { + // Verify resources still exist in unified storage after restart + state.tc.verify(t, helper, true) + }) + } + + t.Logf("Verifying migrations are correctly registered") + verifyRegisteredMigrations(t, helper, false, false) + }) +} + +const ( + migrationScope = "unifiedstorage" + migrationTable = migrationScope + "_migration_log" + + playlistsID = "playlists migration" + foldersAndDashboardsID = "folders and dashboards migration" +) + +var migrationIDsToDefault = map[string]bool{ + playlistsID: true, + foldersAndDashboardsID: false, +} + +func verifyRegisteredMigrations(t *testing.T, helper *apis.K8sTestHelper, onlyDefault bool, optOut bool) { + getMigrationsQuery := fmt.Sprintf("SELECT migration_id FROM %s", migrationTable) + createTableMigrationID := fmt.Sprintf("create %s table", migrationTable) + expectedMigrationIDs := []string{createTableMigrationID} + for id, enabled := range migrationIDsToDefault { + if onlyDefault && !enabled { + continue + } + if optOut { + continue + } + expectedMigrationIDs = append(expectedMigrationIDs, id) + } + rows, err := helper.GetEnv().SQLStore.GetEngine().DB().Query(getMigrationsQuery) + require.NoError(t, err) + defer func() { + require.NoError(t, rows.Close()) + }() + + migrationIDs := make(map[string]struct{}) + for rows.Next() { + var migrationID string + require.NoError(t, rows.Scan(&migrationID)) + require.Contains(t, expectedMigrationIDs, migrationID) + migrationIDs[migrationID] = struct{}{} + } + require.NoError(t, rows.Err()) + require.Len(t, migrationIDs, len(expectedMigrationIDs)) } // verifyResourceCount verifies that the expected number of resources exist in K8s storage diff --git a/pkg/storage/unified/migrations/resources.go b/pkg/storage/unified/migrations/resources.go index 0a4dcc7766e..ea32bb54322 100644 --- a/pkg/storage/unified/migrations/resources.go +++ b/pkg/storage/unified/migrations/resources.go @@ -7,7 +7,9 @@ import ( folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" playlists "github.com/grafana/grafana/apps/playlist/pkg/apis/playlist/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy" + sqlstoremigrator "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -17,7 +19,13 @@ type ResourceDefinition struct { MigratorFunc string // Name of the method: "MigrateFolders", "MigrateDashboards", etc. } -var registeredResources = []ResourceDefinition{ +type migrationDefinition struct { + name string + resources []string + registerFunc func(mg *sqlstoremigrator.Migrator, migrator UnifiedMigrator, client resource.ResourceClient) +} + +var resourceRegistry = []ResourceDefinition{ { GroupResource: schema.GroupResource{Group: folders.GROUP, Resource: folders.RESOURCE}, MigratorFunc: "MigrateFolders", @@ -36,9 +44,50 @@ var registeredResources = []ResourceDefinition{ }, } +var migrationRegistry = []migrationDefinition{ + { + name: "playlists", + resources: []string{setting.PlaylistResource}, + registerFunc: registerPlaylistMigration, + }, + { + name: "folders and dashboards", + resources: []string{setting.FolderResource, setting.DashboardResource}, + registerFunc: registerDashboardAndFolderMigration, + }, +} + +func registerMigrations(cfg *setting.Cfg, mg *sqlstoremigrator.Migrator, migrator UnifiedMigrator, client resource.ResourceClient) error { + for _, migration := range migrationRegistry { + var ( + hasValue bool + allEnabled bool + ) + + for _, res := range migration.resources { + enabled := cfg.UnifiedStorage[res].EnableMigration + if !hasValue { + allEnabled = enabled + hasValue = true + continue + } + if enabled != allEnabled { + return fmt.Errorf("cannot migrate resources separately: %v migration must be either all enabled or all disabled", migration.resources) + } + } + + if !allEnabled { + logger.Info("Migration is disabled in config, skipping", "migration", migration.name) + continue + } + migration.registerFunc(mg, migrator, client) + } + return nil +} + func getResourceDefinition(group, resource string) *ResourceDefinition { - for i := range registeredResources { - r := ®isteredResources[i] + for i := range resourceRegistry { + r := &resourceRegistry[i] if r.GroupResource.Group == group && r.GroupResource.Resource == resource { return r } @@ -80,13 +129,13 @@ func getMigratorFunc(accessor legacy.MigrationDashboardAccessor, group, resource func validateRegisteredResources() error { registeredMap := make(map[string]bool) - for _, gr := range registeredResources { + for _, gr := range resourceRegistry { key := fmt.Sprintf("%s.%s", gr.GroupResource.Resource, gr.GroupResource.Group) registeredMap[key] = true } var missing []string - for _, expected := range setting.MigratedUnifiedResources { + for expected := range setting.MigratedUnifiedResources { if !registeredMap[expected] { missing = append(missing, expected) } diff --git a/pkg/storage/unified/migrations/resources_test.go b/pkg/storage/unified/migrations/resources_test.go new file mode 100644 index 00000000000..5a519e9ad62 --- /dev/null +++ b/pkg/storage/unified/migrations/resources_test.go @@ -0,0 +1,92 @@ +package migrations + +import ( + "testing" + + sqlstoremigrator "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/stretchr/testify/require" +) + +// TestRegisterMigrations exercises registerMigrations with various EnableMigration configs using a table-driven test. +func TestRegisterMigrations(t *testing.T) { + origRegistry := migrationRegistry + t.Cleanup(func() { migrationRegistry = origRegistry }) + + // helper to build a fake registry with custom register funcs that bump counters + makeFakeRegistry := func(migrationCalls map[string]int) []migrationDefinition { + return []migrationDefinition{ + { + name: "playlists", + resources: []string{setting.PlaylistResource}, + registerFunc: func(mg *sqlstoremigrator.Migrator, migrator UnifiedMigrator, client resource.ResourceClient) { + migrationCalls["playlists"]++ + }, + }, + { + name: "folders and dashboards", + resources: []string{setting.FolderResource, setting.DashboardResource}, + registerFunc: func(mg *sqlstoremigrator.Migrator, migrator UnifiedMigrator, client resource.ResourceClient) { + migrationCalls["folders and dashboards"]++ + }, + }, + } + } + + // Build a minimal cfg with UnifiedStorage entries used by registerMigrations + makeCfg := func(vals map[string]bool) *setting.Cfg { + cfg := &setting.Cfg{UnifiedStorage: make(map[string]setting.UnifiedStorageConfig)} + for k, v := range vals { + cfg.UnifiedStorage[k] = setting.UnifiedStorageConfig{EnableMigration: v} + } + return cfg + } + + // Table of scenarios + tests := []struct { + name string + enablePlaylist bool + enableFolder bool + enableDashboard bool + wantPlaylistCalls int + wantFDCalls int + wantErr bool + }{ + {name: "playlists enabled", enablePlaylist: true, wantPlaylistCalls: 1}, + {name: "playlists disabled", enablePlaylist: false, wantPlaylistCalls: 0}, + {name: "folders+dashboards both enabled", enableFolder: true, enableDashboard: true, wantFDCalls: 1}, + {name: "folders enabled, dashboards disabled (mismatch)", enableFolder: true, enableDashboard: false, wantFDCalls: 0, wantErr: true}, + {name: "folders disabled, dashboards enabled (mismatch)", enableFolder: false, enableDashboard: true, wantFDCalls: 0, wantErr: true}, + {name: "folders+dashboards both disabled", enableFolder: false, enableDashboard: false, wantFDCalls: 0}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + migrationCalls := map[string]int{ + "playlists": 0, + "folders and dashboards": 0, + } + + migrationRegistry = makeFakeRegistry(migrationCalls) + + cfg := makeCfg(map[string]bool{ + setting.PlaylistResource: tt.enablePlaylist, + setting.FolderResource: tt.enableFolder, + setting.DashboardResource: tt.enableDashboard, + }) + + // We pass nils for migrator dependencies because our fake registerFuncs don't use them + err := registerMigrations(cfg, nil, nil, nil) + + if tt.wantErr { + require.Error(t, err, "expected error for mismatched enablement") + } else { + require.NoError(t, err, "unexpected error") + } + + require.Equal(t, tt.wantPlaylistCalls, migrationCalls["playlists"], "playlists register call count") + require.Equal(t, tt.wantFDCalls, migrationCalls["folders and dashboards"], "folders+dashboards register call count") + }) + } +} diff --git a/pkg/storage/unified/migrations/service.go b/pkg/storage/unified/migrations/service.go index 1fda9b42593..4ac9cdad218 100644 --- a/pkg/storage/unified/migrations/service.go +++ b/pkg/storage/unified/migrations/service.go @@ -3,7 +3,6 @@ package migrations import ( "context" "fmt" - "os" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/kvstore" @@ -49,34 +48,25 @@ func ProvideUnifiedStorageMigrationService( } func (p *UnifiedStorageMigrationServiceImpl) Run(ctx context.Context) error { - // TODO: temporary skip migrations in test environments to prevent integration test timeouts. - if os.Getenv("GRAFANA_TEST_DB") != "" { - return nil - } - // skip migrations if disabled in config if p.cfg.DisableDataMigrations { metrics.MUnifiedStorageMigrationStatus.Set(1) logger.Info("Data migrations are disabled, skipping") return nil - } else { - metrics.MUnifiedStorageMigrationStatus.Set(2) - logger.Info("Data migrations not yet enforced, skipping") } - - // TODO: Re-enable once migrations are ready - // TODO: add guarantee that this only runs once - // return RegisterMigrations(p.migrator, p.cfg, p.sqlStore, p.client) - return nil + logger.Info("Running migrations for unified storage") + metrics.MUnifiedStorageMigrationStatus.Set(3) + return RegisterMigrations(ctx, p.migrator, p.cfg, p.sqlStore, p.client) } func RegisterMigrations( + ctx context.Context, migrator UnifiedMigrator, cfg *setting.Cfg, sqlStore db.DB, client resource.ResourceClient, ) error { - ctx, span := tracer.Start(context.Background(), "storage.unified.RegisterMigrations") + ctx, span := tracer.Start(ctx, "storage.unified.RegisterMigrations") defer span.End() mg := sqlstoremigrator.NewScopedMigrator(sqlStore.GetEngine(), cfg, "unifiedstorage") mg.AddCreateMigration() @@ -89,12 +79,19 @@ func RegisterMigrations( return err } - // Register resource migrations - registerDashboardAndFolderMigration(mg, migrator, client) - registerPlaylistMigration(mg, migrator, client) + if err := registerMigrations(cfg, mg, migrator, client); err != nil { + return err + } // Run all registered migrations (blocking) sec := cfg.Raw.Section("database") + db := mg.DBEngine.DB().DB + maxOpenConns := db.Stats().MaxOpenConnections + if maxOpenConns <= 2 { + // migrations require at least 3 connections due to extra GRPC connections + db.SetMaxOpenConns(3) + defer db.SetMaxOpenConns(maxOpenConns) + } if err := mg.RunMigrations(ctx, sec.Key("migration_locking").MustBool(true), sec.Key("locking_attempt_timeout_sec").MustInt()); err != nil { diff --git a/pkg/tests/api/alerting/api_convert_prometheus_test.go b/pkg/tests/api/alerting/api_convert_prometheus_test.go index 4fc9151602f..f33ce55d851 100644 --- a/pkg/tests/api/alerting/api_convert_prometheus_test.go +++ b/pkg/tests/api/alerting/api_convert_prometheus_test.go @@ -1166,11 +1166,12 @@ func TestIntegrationConvertPrometheusEndpoints_Editor(t *testing.T) { testinfra.SQLiteIntegrationTest(t) dir, gpath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ - DisableLegacyAlerting: true, - EnableUnifiedAlerting: true, - DisableAnonymous: true, - AppModeProduction: true, - EnableRecordingRules: true, + DisableAuthZClientCache: true, + DisableLegacyAlerting: true, + EnableUnifiedAlerting: true, + DisableAnonymous: true, + AppModeProduction: true, + EnableRecordingRules: true, }) grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, gpath) diff --git a/pkg/tests/api/alerting/api_provisioning_test.go b/pkg/tests/api/alerting/api_provisioning_test.go index 20484a38bf4..e952740f2e6 100644 --- a/pkg/tests/api/alerting/api_provisioning_test.go +++ b/pkg/tests/api/alerting/api_provisioning_test.go @@ -1192,10 +1192,11 @@ func TestIntegrationExportFileProvisionContactPoints(t *testing.T) { func TestIntegrationFullpath(t *testing.T) { dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ - DisableLegacyAlerting: true, - EnableUnifiedAlerting: true, - DisableAnonymous: true, - AppModeProduction: true, + DisableAuthZClientCache: true, + DisableLegacyAlerting: true, + EnableUnifiedAlerting: true, + DisableAnonymous: true, + AppModeProduction: true, }) grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p) diff --git a/pkg/tests/api/alerting/api_ruler_test.go b/pkg/tests/api/alerting/api_ruler_test.go index 9e8855d8f19..a05829976f4 100644 --- a/pkg/tests/api/alerting/api_ruler_test.go +++ b/pkg/tests/api/alerting/api_ruler_test.go @@ -53,10 +53,11 @@ func TestIntegrationAlertRulePermissions(t *testing.T) { testinfra.SQLiteIntegrationTest(t) dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ - DisableLegacyAlerting: true, - EnableUnifiedAlerting: true, - DisableAnonymous: true, - AppModeProduction: true, + DisableAuthZClientCache: true, + DisableLegacyAlerting: true, + EnableUnifiedAlerting: true, + DisableAnonymous: true, + AppModeProduction: true, }) grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p) @@ -360,10 +361,11 @@ func TestIntegrationAlertRuleNestedPermissions(t *testing.T) { testinfra.SQLiteIntegrationTest(t) dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ - DisableLegacyAlerting: true, - EnableUnifiedAlerting: true, - DisableAnonymous: true, - AppModeProduction: true, + DisableAuthZClientCache: true, + DisableLegacyAlerting: true, + EnableUnifiedAlerting: true, + DisableAnonymous: true, + AppModeProduction: true, }) grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p) @@ -1429,10 +1431,11 @@ func TestIntegrationRuleGroupSequence(t *testing.T) { testinfra.SQLiteIntegrationTest(t) dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ - DisableLegacyAlerting: true, - EnableUnifiedAlerting: true, - DisableAnonymous: true, - AppModeProduction: true, + DisableAuthZClientCache: true, + DisableLegacyAlerting: true, + EnableUnifiedAlerting: true, + DisableAnonymous: true, + AppModeProduction: true, }) grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) diff --git a/pkg/tests/api/annotations/annotations_test.go b/pkg/tests/api/annotations/annotations_test.go index 89e7131d4b5..6391d37d762 100644 --- a/pkg/tests/api/annotations/annotations_test.go +++ b/pkg/tests/api/annotations/annotations_test.go @@ -32,8 +32,9 @@ func TestIntegrationAnnotations(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ - DisableAnonymous: true, - EnableFeatureToggles: []string{featuremgmt.FlagAnnotationPermissionUpdate}, + DisableAuthZClientCache: true, + DisableAnonymous: true, + EnableFeatureToggles: []string{featuremgmt.FlagAnnotationPermissionUpdate}, }) grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) noneUserID := tests.CreateUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ diff --git a/pkg/tests/api/dashboards/api_dashboards_test.go b/pkg/tests/api/dashboards/api_dashboards_test.go index fcfb09cb5ba..84ae8c62c62 100644 --- a/pkg/tests/api/dashboards/api_dashboards_test.go +++ b/pkg/tests/api/dashboards/api_dashboards_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -38,8 +39,15 @@ func TestMain(m *testing.M) { func TestIntegrationDashboardServiceValidation(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) + unifiedConfig := make(map[string]setting.UnifiedStorageConfig) + for _, resource := range []string{"folders.folder.grafana.app", "dashboards.dashboard.grafana.app"} { + unifiedConfig[resource] = setting.UnifiedStorageConfig{ + EnableMigration: true, + } + } dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ - DisableAnonymous: true, + DisableAnonymous: true, + UnifiedStorageConfig: unifiedConfig, }) grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) @@ -218,11 +226,12 @@ func TestIntegrationDashboardServiceValidation(t *testing.T) { require.NoError(t, err) }) - t.Run("When updating uid with id", func(t *testing.T) { + dashboardWithDuplicatedLegacyAnnotation := "new-uid" + t.Run("When saving a dashboard with an already used legacy ID", func(t *testing.T) { resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{ "dashboard": map[string]interface{}{ "id": savedDashInFolder.ID, // nolint:staticcheck - "uid": "new-uid", + "uid": dashboardWithDuplicatedLegacyAnnotation, "title": "Updated title", }, "folderUid": savedDashInFolder.FolderUID, @@ -234,7 +243,48 @@ func TestIntegrationDashboardServiceValidation(t *testing.T) { require.NoError(t, err) }) - t.Run("When updating uid with a dashboard already using that uid", func(t *testing.T) { + t.Run("When updating a dashboard with legacy ID in multiple dashboards", func(t *testing.T) { + resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{ + "dashboard": map[string]interface{}{ + "id": savedDashInFolder.ID, // nolint:staticcheck + "uid": savedDashInGeneralFolder.UID, + "title": "Updated title", + }, + "folderUid": savedDashInFolder.FolderUID, + "overwrite": true, + }) + require.NoError(t, err) + assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) + err = resp.Body.Close() + require.NoError(t, err) + // Delete the dashboard with duplicated legacy ID annotation + u := fmt.Sprintf("http://admin:admin@%s/api/dashboards/uid/%s", grafanaListedAddr, dashboardWithDuplicatedLegacyAnnotation) + req, err := http.NewRequest("DELETE", u, nil) + require.NoError(t, err) + resp, err = http.DefaultClient.Do(req) + require.NoError(t, err) + err = resp.Body.Close() + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + }) + + t.Run("When updating a dashboard already using that uid", func(t *testing.T) { + resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{ + "dashboard": map[string]interface{}{ + "id": savedDashInFolder.ID, + "uid": savedDashInFolder.UID, + "title": "Dashboard with existing UID", + }, + "folderUid": savedDashInFolder.FolderUID, + "overwrite": true, + }) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + err = resp.Body.Close() + require.NoError(t, err) + }) + + t.Run("When updating id with a dashboard already using that uid", func(t *testing.T) { resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{ "dashboard": map[string]interface{}{ "id": savedDashInFolder.ID, // nolint:staticcheck @@ -268,8 +318,9 @@ func TestIntegrationDashboardServiceValidation(t *testing.T) { require.NoError(t, err) }) + // Obs: in legacy, the dashboard request would fail + // After the dashboard is created, the user can see that there is an error with the library panel and can remove them manually t.Run("When creating a dashboard that references a non-existent library panel", func(t *testing.T) { - originalCount := getDashboardCount(t, grafanaListedAddr, "admin", "admin") resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{ "dashboard": map[string]interface{}{ "title": "Bad dashboard", @@ -285,15 +336,11 @@ func TestIntegrationDashboardServiceValidation(t *testing.T) { }, }) require.NoError(t, err) - assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) - body, err := io.ReadAll(resp.Body) + assert.Equal(t, http.StatusOK, resp.StatusCode) + _, err = io.ReadAll(resp.Body) require.NoError(t, err) - require.Contains(t, string(body), "library element could not be found") err = resp.Body.Close() require.NoError(t, err) - - // A new dashboard is not created in this situation. - require.Equal(t, originalCount, getDashboardCount(t, grafanaListedAddr, "admin", "admin")) }) } @@ -332,7 +379,7 @@ func TestIntegrationDashboardQuota(t *testing.T) { dashboardDTO := &plugindashboards.PluginDashboard{} err = json.Unmarshal(b, dashboardDTO) require.NoError(t, err) - require.EqualValues(t, 1, dashboardDTO.DashboardId) + require.EqualValues(t, "just testing", dashboardDTO.Title) }) t.Run("when quota limit exceeds importing a dashboard should fail", func(t *testing.T) { @@ -421,7 +468,7 @@ providers: dashboardUID = d.UID dashboardID = d.ID // nolint:staticcheck } - assert.Equal(t, int64(1), dashboardID) + assert.Len(t, *dashboardList, 1) testCases := []struct { desc string @@ -781,7 +828,7 @@ func TestIntegrationImportDashboardWithLibraryPanels(t *testing.T) { }, { "id": 2, - "title": "Library Panel 2", + "title": "Library Panel 2", "type": "stat", "gridPos": {"h": 8, "w": 12, "x": 12, "y": 0}, "libraryPanel": { @@ -805,7 +852,7 @@ func TestIntegrationImportDashboardWithLibraryPanels(t *testing.T) { } }, "test-lib-panel-2": { - "uid": "test-lib-panel-2", + "uid": "test-lib-panel-2", "name": "Test Library Panel 2", "kind": 1, "type": "stat", @@ -1011,26 +1058,12 @@ func postDashboard(t *testing.T, grafanaListedAddr, user, password string, paylo return http.Post(u, "application/json", bytes.NewBuffer(payloadBytes)) // nolint:gosec } -func getDashboardCount(t *testing.T, grafanaListenAddr, user, password string) int { - endpoint := fmt.Sprintf("http://%s:%s@%s/apis/dashboard.grafana.app/v0alpha1/namespaces/default/search", user, password, grafanaListenAddr) - resp, err := http.Get(endpoint) //nolint:gosec - require.NoError(t, err) - - body, err := io.ReadAll(resp.Body) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - var payload map[string]any - require.NoError(t, json.Unmarshal(body, &payload)) - - return int(payload["totalHits"].(float64)) -} - func TestIntegrationDashboardServicePermissions(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ - DisableAnonymous: true, + DisableAnonymous: true, + DisableAuthZClientCache: true, }) grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path) tests.CreateUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ diff --git a/pkg/tests/apis/collections/stars_test.go b/pkg/tests/apis/collections/stars_test.go index 573da05a00b..949e3d6c6ac 100644 --- a/pkg/tests/apis/collections/stars_test.go +++ b/pkg/tests/apis/collections/stars_test.go @@ -42,9 +42,10 @@ func TestIntegrationStars(t *testing.T) { } helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: false, // required for experimental APIs - DisableAnonymous: true, - EnableFeatureToggles: flags, + DisableDataMigrations: true, + AppModeProduction: false, // required for experimental APIs + DisableAnonymous: true, + EnableFeatureToggles: flags, UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ "dashboards.dashboard.grafana.app": { DualWriterMode: mode, diff --git a/pkg/tests/apis/dashboard/dashboards_test.go b/pkg/tests/apis/dashboard/dashboards_test.go index 5d5d04aa535..5e34d28ba9f 100644 --- a/pkg/tests/apis/dashboard/dashboards_test.go +++ b/pkg/tests/apis/dashboard/dashboards_test.go @@ -37,7 +37,8 @@ func TestMain(m *testing.M) { func runDashboardTest(t *testing.T, mode rest.DualWriterMode, gvr schema.GroupVersionResource) { t.Run("simple crud+list", func(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - DisableAnonymous: true, + DisableAnonymous: true, + DisableDataMigrations: true, UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ "dashboards.dashboard.grafana.app": { DualWriterMode: mode, @@ -194,7 +195,9 @@ func TestIntegrationLegacySupport(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) ctx := context.Background() - helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{}) + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + DisableDataMigrations: true, + }) clientV0 := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, @@ -500,9 +503,10 @@ func runDashboardSearchTest(t *testing.T, mode rest.DualWriterMode) { ctx := context.Background() helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + AppModeProduction: true, + DisableDataMigrations: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ "dashboards.dashboard.grafana.app": {DualWriterMode: mode}, "folders.folder.grafana.app": {DualWriterMode: mode}, diff --git a/pkg/tests/apis/dashboard/integration/api_validation_test.go b/pkg/tests/apis/dashboard/integration/api_validation_test.go index 9e087cb76a6..36461ea910e 100644 --- a/pkg/tests/apis/dashboard/integration/api_validation_test.go +++ b/pkg/tests/apis/dashboard/integration/api_validation_test.go @@ -68,7 +68,8 @@ func TestIntegrationDashboardAPIValidation(t *testing.T) { t.Run(fmt.Sprintf("DualWriterMode %d", dualWriterMode), func(t *testing.T) { // Create a K8sTestHelper which will set up a real API server helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - DisableAnonymous: true, + DisableDataMigrations: true, + DisableAnonymous: true, EnableFeatureToggles: []string{ featuremgmt.FlagKubernetesDashboards, // Enable FE-only dashboard feature flag }, @@ -105,7 +106,8 @@ func TestIntegrationDashboardAPIValidation(t *testing.T) { t.Run(fmt.Sprintf("DualWriterMode %d - kubernetesDashboards disabled", dualWriterMode), func(t *testing.T) { // Create a K8sTestHelper which will set up a real API server helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - DisableAnonymous: true, + DisableDataMigrations: true, + DisableAnonymous: true, DisableFeatureToggles: []string{ featuremgmt.FlagKubernetesDashboards, }, @@ -138,7 +140,8 @@ func TestIntegrationDashboardAPIAuthorization(t *testing.T) { for _, dualWriterMode := range dualWriterModes { t.Run(fmt.Sprintf("DualWriterMode %d", dualWriterMode), func(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - DisableAnonymous: true, + DisableDataMigrations: true, + DisableAnonymous: true, UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ "dashboards.dashboard.grafana.app": { DualWriterMode: dualWriterMode, @@ -185,7 +188,8 @@ func TestIntegrationDashboardAPI(t *testing.T) { t.Run(fmt.Sprintf("DualWriterMode %d", dualWriterMode), func(t *testing.T) { // Create a K8sTestHelper which will set up a real API server helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - DisableAnonymous: true, + DisableDataMigrations: true, + DisableAnonymous: true, EnableFeatureToggles: []string{ featuremgmt.FlagKubernetesDashboards, }, diff --git a/pkg/tests/apis/dashboard/integration/library_panels_api_validation_test.go b/pkg/tests/apis/dashboard/integration/library_panels_api_validation_test.go index dd85fdb2400..32548d593ea 100644 --- a/pkg/tests/apis/dashboard/integration/library_panels_api_validation_test.go +++ b/pkg/tests/apis/dashboard/integration/library_panels_api_validation_test.go @@ -29,7 +29,8 @@ func TestIntegrationLibraryPanelConnections(t *testing.T) { for _, dualWriterMode := range dualWriterModes { t.Run(fmt.Sprintf("DualWriterMode %d", dualWriterMode), func(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - DisableAnonymous: true, + DisableDataMigrations: true, + DisableAnonymous: true, EnableFeatureToggles: []string{ "kubernetesLibraryPanels", }, @@ -93,7 +94,8 @@ func TestIntegrationLibraryElementPermissions(t *testing.T) { for _, dualWriterMode := range dualWriterModes { t.Run(fmt.Sprintf("DualWriterMode %d", dualWriterMode), func(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - DisableAnonymous: true, + DisableDataMigrations: true, + DisableAnonymous: true, EnableFeatureToggles: []string{ "kubernetesLibraryPanels", "grafanaAPIServerWithExperimentalAPIs", // needed until we move it to v0beta1 at least (currently v0alpha1) @@ -296,7 +298,8 @@ func TestIntegrationLibraryPanelConnectionsWithFolderAccess(t *testing.T) { for _, dualWriterMode := range dualWriterModes { t.Run(fmt.Sprintf("DualWriterMode %d", dualWriterMode), func(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - DisableAnonymous: true, + DisableDataMigrations: true, + DisableAnonymous: true, EnableFeatureToggles: []string{ "kubernetesLibraryPanels", }, diff --git a/pkg/tests/apis/dashboard/search_test.go b/pkg/tests/apis/dashboard/search_test.go index 09d23398c3f..3fe35c51b3a 100644 --- a/pkg/tests/apis/dashboard/search_test.go +++ b/pkg/tests/apis/dashboard/search_test.go @@ -37,9 +37,10 @@ func runSearchPermissionTest(t *testing.T, mode rest.DualWriterMode) { ctx := context.Background() helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ "dashboards.dashboard.grafana.app": {DualWriterMode: mode}, "folders.folder.grafana.app": {DualWriterMode: mode}, diff --git a/pkg/tests/apis/folder/folder_tree_test.go b/pkg/tests/apis/folder/folder_tree_test.go index b1d38ab3918..1b3d5df37f2 100644 --- a/pkg/tests/apis/folder/folder_tree_test.go +++ b/pkg/tests/apis/folder/folder_tree_test.go @@ -48,9 +48,10 @@ func TestIntegrationFolderTree(t *testing.T) { for _, mode := range modes { t.Run(fmt.Sprintf("mode %d", mode), func(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ "dashboards.dashboard.grafana.app": { DualWriterMode: mode, diff --git a/pkg/tests/apis/folder/folders_test.go b/pkg/tests/apis/folder/folders_test.go index 1cc58c4bdbd..ce7dd5f6795 100644 --- a/pkg/tests/apis/folder/folders_test.go +++ b/pkg/tests/apis/folder/folders_test.go @@ -139,9 +139,10 @@ func TestIntegrationFoldersApp(t *testing.T) { t.Run(fmt.Sprintf("with dual write (unified storage, mode %v)", modeDw), func(t *testing.T) { doFolderTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: modeDw, @@ -153,9 +154,10 @@ func TestIntegrationFoldersApp(t *testing.T) { t.Run(fmt.Sprintf("with dual write (unified storage, mode %v, create nested folders)", modeDw), func(t *testing.T) { doNestedCreateTest(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: modeDw, @@ -166,9 +168,10 @@ func TestIntegrationFoldersApp(t *testing.T) { t.Run(fmt.Sprintf("with dual write (unified storage, mode %v, create existing folder)", modeDw), func(t *testing.T) { doCreateDuplicateFolderTest(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: modeDw, @@ -179,9 +182,10 @@ func TestIntegrationFoldersApp(t *testing.T) { t.Run(fmt.Sprintf("when creating a folder, mode %v, it should trim leading and trailing spaces", modeDw), func(t *testing.T) { doCreateEnsureTitleIsTrimmedTest(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: modeDw, @@ -192,9 +196,10 @@ func TestIntegrationFoldersApp(t *testing.T) { t.Run(fmt.Sprintf("with dual write (unified storage, mode %v, create circular reference folder)", modeDw), func(t *testing.T) { doCreateCircularReferenceFolderTest(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: modeDw, @@ -218,9 +223,10 @@ func TestIntegrationFoldersApp(t *testing.T) { for _, mode := range modes { t.Run(fmt.Sprintf("mode %d", mode), func(t *testing.T) { doListFoldersTest(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: mode, @@ -735,9 +741,10 @@ func TestIntegrationFolderCreatePermissions(t *testing.T) { t.Run(fmt.Sprintf("Mode_%d", mode), func(t *testing.T) { modeDw := grafanarest.DualWriterMode(mode) helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: modeDw, @@ -862,9 +869,10 @@ func TestIntegrationFolderGetPermissions(t *testing.T) { t.Run(fmt.Sprintf("Mode_%d", mode), func(t *testing.T) { modeDw := grafanarest.DualWriterMode(mode) helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: modeDw, @@ -1079,9 +1087,10 @@ func TestIntegrationFoldersCreateAPIEndpointK8S(t *testing.T) { for mode := 0; mode <= 4; mode++ { modeDw := grafanarest.DualWriterMode(mode) helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: modeDw, @@ -1254,9 +1263,10 @@ func TestIntegrationFoldersGetAPIEndpointK8S(t *testing.T) { modeDw := grafanarest.DualWriterMode(mode) helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: modeDw, @@ -1366,9 +1376,10 @@ func TestIntegrationFolderDeletionBlockedByLibraryElements(t *testing.T) { modeDw := grafanarest.DualWriterMode(mode) helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: modeDw, @@ -1446,9 +1457,10 @@ func TestIntegrationRootFolderDeletionBlockedByLibraryElementsInSubfolder(t *tes modeDw := grafanarest.DualWriterMode(mode) helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: modeDw, @@ -1542,9 +1554,10 @@ func TestIntegrationFolderDeletionBlockedByConnectedLibraryPanels(t *testing.T) modeDw := grafanarest.DualWriterMode(mode) helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: modeDw, @@ -1620,9 +1633,10 @@ func TestIntegrationFolderDeletionWithDanglingLibraryPanels(t *testing.T) { modeDw := grafanarest.DualWriterMode(mode) helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: modeDw, @@ -1901,9 +1915,10 @@ func TestIntegrationDeleteNestedFoldersPostorder(t *testing.T) { t.Run(fmt.Sprintf("Mode %d: Delete nested folder hierarchy in postorder", mode), func(t *testing.T) { modeDw := grafanarest.DualWriterMode(mode) helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: modeDw, @@ -2027,9 +2042,10 @@ func TestIntegrationDeleteFolderWithProvisionedDashboards(t *testing.T) { t.Run(fmt.Sprintf("Mode %d: Delete provisioned folders and dashboards", mode), func(t *testing.T) { modeDw := grafanarest.DualWriterMode(mode) ops := testinfra.GrafanaOpts{ - DisableAnonymous: true, - AppModeProduction: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + DisableAnonymous: true, + AppModeProduction: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: modeDw, @@ -2131,11 +2147,14 @@ func TestIntegrationDeleteFolderWithProvisionedDashboards(t *testing.T) { // Test that folders created during provisioning using the dual writer have the // appropriate labels and annotations in unified storage. func TestIntegrationProvisionedFolderPropagatesLabelsAndAnnotations(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + mode3 := grafanarest.DualWriterMode(3) ops := testinfra.GrafanaOpts{ - DisableAnonymous: true, - AppModeProduction: true, - APIServerStorageType: "unified", + DisableDataMigrations: true, + DisableAnonymous: true, + AppModeProduction: true, + APIServerStorageType: "unified", UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ folders.RESOURCEGROUP: { DualWriterMode: mode3, diff --git a/pkg/tests/apis/openapi_snapshots/playlist.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/playlist.grafana.app-v0alpha1.json index dd1b486823e..dee1a56d6c0 100644 --- a/pkg/tests/apis/openapi_snapshots/playlist.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/playlist.grafana.app-v0alpha1.json @@ -40,7 +40,7 @@ "tags": [ "Playlist" ], - "description": "list objects of kind Playlist", + "description": "list or watch objects of kind Playlist", "operationId": "listPlaylist", "parameters": [ { @@ -1850,6 +1850,32 @@ "description": "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", "type": "string", "format": "date-time" + }, + "io.k8s.apimachinery.pkg.apis.meta.v1.WatchEvent": { + "description": "Event represents a single event to a watched resource.", + "type": "object", + "required": [ + "type", + "object" + ], + "properties": { + "object": { + "description": "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context.", + "allOf": [ + { + "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension" + } + ] + }, + "type": { + "type": "string", + "default": "" + } + } + }, + "io.k8s.apimachinery.pkg.runtime.RawExtension": { + "description": "RawExtension is used to hold extensions in external versions.\n\nTo use this, make a field which has RawExtension as its type in your external, versioned struct, and Object in your internal struct. You also need to register your various plugin types.\n\n// Internal package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.Object `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// External package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.RawExtension `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// On the wire, the JSON will look something like this:\n\n\t{\n\t\t\"kind\":\"MyAPIObject\",\n\t\t\"apiVersion\":\"v1\",\n\t\t\"myPlugin\": {\n\t\t\t\"kind\":\"PluginA\",\n\t\t\t\"aOption\":\"foo\",\n\t\t},\n\t}\n\nSo what happens? Decode first uses json or yaml to unmarshal the serialized data into your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked. The next step is to copy (using pkg/conversion) into the internal struct. The runtime package's DefaultScheme has conversion functions installed which will unpack the JSON stored in RawExtension, turning it into the correct object type, and storing it in the Object. (TODO: In the case where the object is of an unknown type, a runtime.Unknown object will be created and stored.)", + "type": "object" } } } diff --git a/pkg/tests/apis/playlist/playlist_test.go b/pkg/tests/apis/playlist/playlist_test.go index 057452eb7f9..2611624debb 100644 --- a/pkg/tests/apis/playlist/playlist_test.go +++ b/pkg/tests/apis/playlist/playlist_test.go @@ -54,47 +54,48 @@ func TestIntegrationPlaylist(t *testing.T) { require.NoError(t, err) // t.Logf("%s", disco) require.JSONEq(t, `[ - { - "version": "v0alpha1", - "freshness": "Current", - "resources": [ - { - "resource": "playlists", - "responseKind": { - "group": "", - "kind": "Playlist", - "version": "" - }, - "scope": "Namespaced", - "singularResource": "playlist", - "subresources": [ - { - "responseKind": { - "group": "", - "kind": "Playlist", - "version": "" - }, - "subresource": "status", - "verbs": [ - "get", - "patch", - "update" - ] - } - ], - "verbs": [ - "create", - "delete", - "deletecollection", - "get", - "list", - "patch", - "update" - ] - } - ] - } - ]`, disco) + { + "freshness": "Current", + "resources": [ + { + "resource": "playlists", + "responseKind": { + "group": "", + "kind": "Playlist", + "version": "" + }, + "scope": "Namespaced", + "singularResource": "playlist", + "subresources": [ + { + "responseKind": { + "group": "", + "kind": "Playlist", + "version": "" + }, + "subresource": "status", + "verbs": [ + "get", + "patch", + "update" + ] + } + ], + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ] + } + ], + "version": "v0alpha1" + } + ]`, disco) }) t.Run("with k8s api flag", func(t *testing.T) { @@ -106,9 +107,10 @@ func TestIntegrationPlaylist(t *testing.T) { t.Run("with dual write (file, mode 0)", func(t *testing.T) { doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "file", // write the files to disk + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "file", // write the files to disk UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ RESOURCEGROUP: { DualWriterMode: grafanarest.Mode0, @@ -119,9 +121,10 @@ func TestIntegrationPlaylist(t *testing.T) { t.Run("with dual write (file, mode 1)", func(t *testing.T) { doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "file", // write the files to disk + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "file", // write the files to disk UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ RESOURCEGROUP: { DualWriterMode: grafanarest.Mode1, @@ -132,9 +135,10 @@ func TestIntegrationPlaylist(t *testing.T) { t.Run("with dual write (file, mode 2)", func(t *testing.T) { doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "file", // write the files to disk + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "file", // write the files to disk UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ RESOURCEGROUP: { DualWriterMode: grafanarest.Mode2, @@ -145,9 +149,10 @@ func TestIntegrationPlaylist(t *testing.T) { t.Run("with dual write (file, mode 3)", func(t *testing.T) { doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "file", // write the files to disk + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "file", // write the files to disk UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ RESOURCEGROUP: { DualWriterMode: grafanarest.Mode3, @@ -158,9 +163,10 @@ func TestIntegrationPlaylist(t *testing.T) { t.Run("with dual write (file, mode 5)", func(t *testing.T) { helper := doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: "file", // write the files to disk + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "file", // write the files to disk UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ RESOURCEGROUP: { DualWriterMode: grafanarest.Mode5, @@ -198,9 +204,10 @@ func TestIntegrationPlaylist(t *testing.T) { t.Run("with dual write (unified storage, mode 0)", func(t *testing.T) { doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: false, // required for unified storage - DisableAnonymous: true, - APIServerStorageType: options.StorageTypeUnified, // use the entity api tables + DisableDataMigrations: true, + AppModeProduction: false, // required for unified storage + DisableAnonymous: true, + APIServerStorageType: options.StorageTypeUnified, // use the entity api tables UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ RESOURCEGROUP: { DualWriterMode: grafanarest.Mode0, @@ -211,18 +218,20 @@ func TestIntegrationPlaylist(t *testing.T) { t.Run("with dual write (unified storage, mode 1)", func(t *testing.T) { doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: false, - DisableAnonymous: true, - APIServerStorageType: options.StorageTypeUnified, - EnableFeatureToggles: []string{}, + DisableDataMigrations: true, + AppModeProduction: false, + DisableAnonymous: true, + APIServerStorageType: options.StorageTypeUnified, + EnableFeatureToggles: []string{}, })) }) t.Run("with dual write (unified storage, mode 2)", func(t *testing.T) { doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: false, // required for unified storage - DisableAnonymous: true, - APIServerStorageType: options.StorageTypeUnified, // use the entity api tables + DisableDataMigrations: true, + AppModeProduction: false, // required for unified storage + DisableAnonymous: true, + APIServerStorageType: options.StorageTypeUnified, // use the entity api tables UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ RESOURCEGROUP: { DualWriterMode: grafanarest.Mode2, @@ -233,9 +242,10 @@ func TestIntegrationPlaylist(t *testing.T) { t.Run("with dual write (unified storage, mode 3)", func(t *testing.T) { doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: false, // required for unified storage - DisableAnonymous: true, - APIServerStorageType: options.StorageTypeUnified, // use the entity api tables + DisableDataMigrations: true, + AppModeProduction: false, // required for unified storage + DisableAnonymous: true, + APIServerStorageType: options.StorageTypeUnified, // use the entity api tables UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ RESOURCEGROUP: { DualWriterMode: grafanarest.Mode3, @@ -246,9 +256,10 @@ func TestIntegrationPlaylist(t *testing.T) { t.Run("with dual write (unified storage, mode 5)", func(t *testing.T) { doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: false, // required for unified storage - DisableAnonymous: true, - APIServerStorageType: options.StorageTypeUnified, // use the entity api tables + DisableDataMigrations: true, + AppModeProduction: false, // required for unified storage + DisableAnonymous: true, + APIServerStorageType: options.StorageTypeUnified, // use the entity api tables UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ RESOURCEGROUP: { DualWriterMode: grafanarest.Mode5, @@ -262,9 +273,10 @@ func TestIntegrationPlaylist(t *testing.T) { t.Skip("local etcd testing") helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: options.StorageTypeEtcd, // requires etcd running on localhost:2379 + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: options.StorageTypeEtcd, // requires etcd running on localhost:2379 UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ RESOURCEGROUP: { DualWriterMode: grafanarest.Mode0, @@ -288,9 +300,10 @@ func TestIntegrationPlaylist(t *testing.T) { t.Skip("local etcd testing") helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: options.StorageTypeEtcd, // requires etcd running on localhost:2379 + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: options.StorageTypeEtcd, // requires etcd running on localhost:2379 UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ RESOURCEGROUP: { DualWriterMode: grafanarest.Mode1, @@ -314,9 +327,10 @@ func TestIntegrationPlaylist(t *testing.T) { t.Skip("local etcd testing") helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: options.StorageTypeEtcd, // requires etcd running on localhost:2379 + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: options.StorageTypeEtcd, // requires etcd running on localhost:2379 UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ RESOURCEGROUP: { DualWriterMode: grafanarest.Mode2, @@ -340,9 +354,10 @@ func TestIntegrationPlaylist(t *testing.T) { t.Skip("local etcd testing") helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: options.StorageTypeEtcd, // requires etcd running on localhost:2379 + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: options.StorageTypeEtcd, // requires etcd running on localhost:2379 UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ RESOURCEGROUP: { DualWriterMode: grafanarest.Mode3, @@ -366,9 +381,10 @@ func TestIntegrationPlaylist(t *testing.T) { t.Skip("local etcd testing") helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, - DisableAnonymous: true, - APIServerStorageType: options.StorageTypeEtcd, // requires etcd running on localhost:2379 + DisableDataMigrations: true, + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: options.StorageTypeEtcd, // requires etcd running on localhost:2379 UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ RESOURCEGROUP: { DualWriterMode: grafanarest.Mode5, diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index e9795f945ab..c84ddd6fb40 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -362,6 +362,13 @@ func CreateGrafDir(t *testing.T, opts GrafanaOpts) (string, string) { _, err = rbacSect.NewKey("permission_cache", "false") require.NoError(t, err) + if opts.DisableAuthZClientCache { + authzSect, err := cfg.NewSection("authorization") + require.NoError(t, err) + _, err = authzSect.NewKey("cache_ttl", "0") + require.NoError(t, err) + } + analyticsSect, err := cfg.NewSection("analytics") require.NoError(t, err) _, err = analyticsSect.NewKey("intercom_secret", "intercom_secret_at_config") @@ -547,6 +554,8 @@ func CreateGrafDir(t *testing.T, opts GrafanaOpts) (string, string) { require.NoError(t, err) _, err = section.NewKey("dualWriterMode", fmt.Sprintf("%d", v.DualWriterMode)) require.NoError(t, err) + _, err = section.NewKey("enableMigration", fmt.Sprintf("%t", v.EnableMigration)) + require.NoError(t, err) } } if opts.UnifiedStorageEnableSearch { @@ -670,6 +679,7 @@ type GrafanaOpts struct { DisableDataMigrations bool SecretsManagerEnableDBMigrations bool OpenFeatureAPIEnabled bool + DisableAuthZClientCache bool // Allow creating grafana dir beforehand Dir string From fc4c699d85648b9fc3927e8dbd58d3737cf60eef Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 17 Dec 2025 09:21:39 +0000 Subject: [PATCH 182/297] Chore: More backwards compatible changes needed for react 19 (#115422) backwards compatible changes needed for react 19 --- eslint-suppressions.json | 5 ---- .../src/FlameGraph/FlameGraph.test.tsx | 6 ++-- .../src/FlameGraphContainer.test.tsx | 2 +- .../services/pluginExtensions/utils.test.tsx | 28 ++++++++----------- .../components/Select/resetSelectStyles.ts | 18 ++++++------ .../src/components/Table/Table.story.tsx | 11 +++++++- .../app/core/components/Login/LoginCtrl.tsx | 4 +-- .../rule-editor/CloneRuleEditor.test.tsx | 4 +-- .../app/features/explore/spec/query.test.tsx | 2 +- .../app/features/explore/spec/split.test.tsx | 2 +- .../logs/components/LogRowMenuCell.tsx | 7 +++-- .../app/plugins/panel/geomap/GeomapPanel.tsx | 12 +++++--- 12 files changed, 52 insertions(+), 49 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 61bc82f1a7b..cdbeabcc87b 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -763,11 +763,6 @@ "count": 1 } }, - "packages/grafana-ui/src/components/Select/resetSelectStyles.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, "packages/grafana-ui/src/components/Select/types.ts": { "@typescript-eslint/no-explicit-any": { "count": 6 diff --git a/packages/grafana-flamegraph/src/FlameGraph/FlameGraph.test.tsx b/packages/grafana-flamegraph/src/FlameGraph/FlameGraph.test.tsx index 64dc10da72e..b79074cfc6b 100644 --- a/packages/grafana-flamegraph/src/FlameGraph/FlameGraph.test.tsx +++ b/packages/grafana-flamegraph/src/FlameGraph/FlameGraph.test.tsx @@ -1,5 +1,5 @@ import { fireEvent, render, screen } from '@testing-library/react'; -import * as React from 'react'; +import { type ComponentProps, useRef } from 'react'; import { createDataFrame } from '@grafana/data'; @@ -16,14 +16,14 @@ jest.mock('react-use', () => { return { ...reactUse, useMeasure: () => { - const ref = React.useRef(); + const ref = useRef(null); return [ref, { width: 1600 }]; }, }; }); describe('FlameGraph', () => { - function setup(props?: Partial>) { + function setup(props?: Partial>) { const flameGraphData = createDataFrame(data); const container = new FlameGraphDataContainer(flameGraphData, { collapsing: true }); diff --git a/packages/grafana-flamegraph/src/FlameGraphContainer.test.tsx b/packages/grafana-flamegraph/src/FlameGraphContainer.test.tsx index 4fdb00d6748..f7e7c09957c 100644 --- a/packages/grafana-flamegraph/src/FlameGraphContainer.test.tsx +++ b/packages/grafana-flamegraph/src/FlameGraphContainer.test.tsx @@ -21,7 +21,7 @@ jest.mock('@grafana/assistant', () => ({ jest.mock('react-use', () => ({ ...jest.requireActual('react-use'), useMeasure: () => { - const ref = useRef(); + const ref = useRef(null); return [ref, { width: 1600 }]; }, })); diff --git a/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx b/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx index b62ae46f27c..b139fa80a75 100644 --- a/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx +++ b/packages/grafana-runtime/src/services/pluginExtensions/utils.test.tsx @@ -262,24 +262,18 @@ function createComponent( pluginId?: string, id?: string ): ComponentTypeWithExtensionMeta { - function ComponentWithMeta(props: Props) { - if (Implementation) { - return ; + const ComponentWithMeta: ComponentTypeWithExtensionMeta = Object.assign( + Implementation || (() =>
Test
), + { + meta: { + id: id ?? '', + pluginId: pluginId ?? '', + title: '', + description: '', + type: PluginExtensionTypes.component, + } satisfies PluginExtensionComponentMeta, } - - return
Test
; - } - - ComponentWithMeta.displayName = ''; - ComponentWithMeta.propTypes = {}; - ComponentWithMeta.contextTypes = {}; - ComponentWithMeta.meta = { - id: id ?? '', - pluginId: pluginId ?? '', - title: '', - description: '', - type: PluginExtensionTypes.component, - } satisfies PluginExtensionComponentMeta; + ); return ComponentWithMeta; } diff --git a/packages/grafana-ui/src/components/Select/resetSelectStyles.ts b/packages/grafana-ui/src/components/Select/resetSelectStyles.ts index 90ac3877c2a..1432c01e1da 100644 --- a/packages/grafana-ui/src/components/Select/resetSelectStyles.ts +++ b/packages/grafana-ui/src/components/Select/resetSelectStyles.ts @@ -1,9 +1,9 @@ import { useMemo } from 'react'; -import { CSSObjectWithLabel } from 'react-select'; +import { StylesConfig } from 'react-select'; import { GrafanaTheme2 } from '@grafana/data'; -export default function resetSelectStyles(theme: GrafanaTheme2) { +export default function resetSelectStyles(theme: GrafanaTheme2): Partial { return { clearIndicator: () => ({}), container: () => ({}), @@ -13,7 +13,7 @@ export default function resetSelectStyles(theme: GrafanaTheme2) { groupHeading: () => ({}), indicatorsContainer: () => ({}), indicatorSeparator: () => ({}), - input: function (originalStyles: CSSObjectWithLabel) { + input: function (originalStyles) { return { ...originalStyles, color: 'inherit', @@ -27,7 +27,7 @@ export default function resetSelectStyles(theme: GrafanaTheme2) { loadingIndicator: () => ({}), loadingMessage: () => ({}), menu: () => ({}), - menuList: ({ maxHeight }: { maxHeight: number }) => ({ + menuList: ({ maxHeight }) => ({ maxHeight, }), multiValue: () => ({}), @@ -38,7 +38,7 @@ export default function resetSelectStyles(theme: GrafanaTheme2) { multiValueRemove: () => ({}), noOptionsMessage: () => ({}), option: () => ({}), - placeholder: (originalStyles: CSSObjectWithLabel) => ({ + placeholder: (originalStyles) => ({ ...originalStyles, color: theme.colors.text.secondary, }), @@ -47,11 +47,11 @@ export default function resetSelectStyles(theme: GrafanaTheme2) { }; } -export function useCustomSelectStyles(theme: GrafanaTheme2, width: number | string | undefined) { +export function useCustomSelectStyles(theme: GrafanaTheme2, width: number | string | undefined): Partial { return useMemo(() => { return { ...resetSelectStyles(theme), - menuPortal: (base: CSSObjectWithLabel) => { + menuPortal: (base) => { // Would like to correct top position when menu is placed bottom, but have props are not sent to this style function. // Only state is. https://github.com/JedWatson/react-select/blob/master/packages/react-select/src/components/Menu.tsx#L605 return { @@ -60,7 +60,7 @@ export function useCustomSelectStyles(theme: GrafanaTheme2, width: number | stri }; }, //These are required for the menu positioning to function - menu: ({ top, bottom, position }: CSSObjectWithLabel) => { + menu: ({ top, bottom, position }) => { return { top, bottom, @@ -73,7 +73,7 @@ export function useCustomSelectStyles(theme: GrafanaTheme2, width: number | stri width: width ? theme.spacing(width) : '100%', display: width === 'auto' ? 'inline-flex' : 'flex', }), - option: (provided: CSSObjectWithLabel, state: any) => ({ + option: (provided, state) => ({ ...provided, opacity: state.isDisabled ? 0.5 : 1, }), diff --git a/packages/grafana-ui/src/components/Table/Table.story.tsx b/packages/grafana-ui/src/components/Table/Table.story.tsx index b6c97df317e..e3d5e4b56cc 100644 --- a/packages/grafana-ui/src/components/Table/Table.story.tsx +++ b/packages/grafana-ui/src/components/Table/Table.story.tsx @@ -263,7 +263,16 @@ export const Footer: StoryFn = (args) => { ); }; -export const Pagination: StoryFn = (args) => ; +export const Pagination: StoryFn = (args) => { + const theme = useTheme2(); + const data = buildData(theme, {}); + + return ( + + + + ); +}; Pagination.args = { enablePagination: true, }; diff --git a/public/app/core/components/Login/LoginCtrl.tsx b/public/app/core/components/Login/LoginCtrl.tsx index 1a5f3d572e4..1bab9faf64e 100644 --- a/public/app/core/components/Login/LoginCtrl.tsx +++ b/public/app/core/components/Login/LoginCtrl.tsx @@ -119,11 +119,11 @@ export const LoginCtrl = memo(({ resetCode, children }: Props) => { }, []); const login = useCallback( - (formModel: FormModel) => { + async (formModel: FormModel) => { setLoginErrorMessage(undefined); setIsLoggingIn(true); - getBackendSrv() + return getBackendSrv() .post('/login', formModel, { showErrorAlert: false }) .then((result) => { setResult(result); diff --git a/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx b/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx index 2572d9ddfc5..bc2153c38fe 100644 --- a/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx +++ b/public/app/features/alerting/unified/rule-editor/CloneRuleEditor.test.tsx @@ -165,10 +165,10 @@ describe('CloneRuleEditor', function () { ); await waitFor(() => { - expect(ui.inputs.name.get()).toHaveValue('First Ruler Rule (copy)'); + expect(ui.inputs.namespace.get()).toHaveTextContent('namespace-one'); }); + expect(ui.inputs.name.get()).toHaveValue('First Ruler Rule (copy)'); expect(ui.inputs.expr.get()).toHaveValue('vector(1) > 0'); - expect(ui.inputs.namespace.get()).toHaveTextContent('namespace-one'); expect(ui.inputs.group.get()).toHaveTextContent('group1'); expect( byRole('listitem', { diff --git a/public/app/features/explore/spec/query.test.tsx b/public/app/features/explore/spec/query.test.tsx index 2d2ca951ec0..1b185378ab0 100644 --- a/public/app/features/explore/spec/query.test.tsx +++ b/public/app/features/explore/spec/query.test.tsx @@ -50,7 +50,7 @@ describe('Explore: handle running/not running query', () => { jest.mocked(datasources.loki.query).mockReturnValueOnce(makeLogsQueryResponse()); // Make sure we render the logs panel - await screen.findByText(/^Logs$/); + await screen.findByRole('heading', { name: /^Logs$/ }); // Make sure we render the log line await screen.findByText(/custom log line/i); diff --git a/public/app/features/explore/spec/split.test.tsx b/public/app/features/explore/spec/split.test.tsx index e114201108a..0675a382d34 100644 --- a/public/app/features/explore/spec/split.test.tsx +++ b/public/app/features/explore/spec/split.test.tsx @@ -122,7 +122,7 @@ describe('Handles open/close splits and related events in UI and URL', () => { // Make sure we render the logs panel await waitFor(() => { - const logsPanels = screen.getAllByText(/^Logs$/); + const logsPanels = screen.getAllByRole('heading', { name: /^Logs$/ }); expect(logsPanels.length).toBe(2); }); diff --git a/public/app/features/logs/components/LogRowMenuCell.tsx b/public/app/features/logs/components/LogRowMenuCell.tsx index 892a5eea68b..e205c1608e1 100644 --- a/public/app/features/logs/components/LogRowMenuCell.tsx +++ b/public/app/features/logs/components/LogRowMenuCell.tsx @@ -185,11 +185,12 @@ export const LogRowMenuCell = memo( } ); -type AddonOnClickListener = (event: MouseEvent, row: LogRowModel) => void | undefined; +type AddonOnClickListener = (event: MouseEvent, row: LogRowModel) => void | undefined; +type ChildElementProps = Record & { onClick: AddonOnClickListener }; function addClickListenersToNode(nodes: ReactNode[], row: LogRowModel) { return nodes.map((node, index) => { - if (isValidElement(node)) { - const onClick: AddonOnClickListener = node.props.onClick; + if (isValidElement(node)) { + const onClick = node.props.onClick; if (!onClick) { return node; } diff --git a/public/app/plugins/panel/geomap/GeomapPanel.tsx b/public/app/plugins/panel/geomap/GeomapPanel.tsx index eb7d7c7dad4..cb532f0ed95 100644 --- a/public/app/plugins/panel/geomap/GeomapPanel.tsx +++ b/public/app/plugins/panel/geomap/GeomapPanel.tsx @@ -79,7 +79,7 @@ export class GeomapPanel extends Component { this.subs.add( this.props.eventBus.subscribe(PanelEditExitedEvent, (evt) => { if (this.mapDiv && this.props.id === evt.payload) { - this.initMapRef(this.mapDiv); + this.initMapAsync(this.mapDiv); } }) ); @@ -97,7 +97,7 @@ export class GeomapPanel extends Component { }); if (hasDependencies) { - this.initMapRef(this.mapDiv); + this.initMapAsync(this.mapDiv); } } }) @@ -182,7 +182,7 @@ export class GeomapPanel extends Component { if (noRepeatChanged) { if (this.mapDiv) { - this.initMapRef(this.mapDiv); + this.initMapAsync(this.mapDiv); } // Skip other options processing return; @@ -227,7 +227,7 @@ export class GeomapPanel extends Component { this.setState({ legends: this.getLegends() }); } - initMapRef = async (div: HTMLDivElement) => { + initMapAsync = async (div: HTMLDivElement | null) => { if (!div) { // Do not initialize new map or dispose old map return; @@ -437,6 +437,10 @@ export class GeomapPanel extends Component { return legends; } + initMapRef = (div: HTMLDivElement | null) => { + this.initMapAsync(div); + }; + render() { let { ttip, ttipOpen, topRight1, legends, topRight2 } = this.state; const { options } = this.props; From 1b9e0fae8d762b5deb256152acbbbc2bce9fd019 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Wed, 17 Dec 2025 10:25:28 +0100 Subject: [PATCH 183/297] Logs: more analytics (#115330) * Logs: more analytics * LogLineDetails: collect fields data * analytics: report length count * Prettier * LogLineDetailsHeader: track details mode toggle --- .../features/logs/components/otel/formats.ts | 2 +- .../panel/LogLineDetailsComponent.tsx | 18 +++++- .../components/panel/LogLineDetailsHeader.tsx | 6 +- .../logs/components/panel/LogListContext.tsx | 4 +- .../logs/components/panel/analytics.ts | 56 +++++++++++++++++++ 5 files changed, 81 insertions(+), 5 deletions(-) diff --git a/public/app/features/logs/components/otel/formats.ts b/public/app/features/logs/components/otel/formats.ts index c767a69a89e..23e6ec3559f 100644 --- a/public/app/features/logs/components/otel/formats.ts +++ b/public/app/features/logs/components/otel/formats.ts @@ -9,7 +9,7 @@ import { LogListModel, NEWLINES_REGEX } from '../panel/processing'; export const OTEL_PROBE_FIELD = 'severity_number'; const OTEL_LANGUAGE_UNKNOWN = 'unknown'; -function identifyOTelLanguages(logs: LogListModel[] | LogRowModel[]): string[] { +export function identifyOTelLanguages(logs: LogListModel[] | LogRowModel[]): string[] { const languagesSet = new Set(); logs.forEach((log) => { const lang = identifyOTelLanguage(log); diff --git a/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx b/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx index 36bdbf2bdde..0afd2e21369 100644 --- a/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx +++ b/public/app/features/logs/components/panel/LogLineDetailsComponent.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/css'; import { camelCase, groupBy } from 'lodash'; -import { memo, startTransition, useCallback, useMemo, useRef, useState } from 'react'; +import { memo, startTransition, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { DataFrameType, GrafanaTheme2, store, TimeRange } from '@grafana/data'; import { t, Trans } from '@grafana/i18n'; @@ -18,6 +18,7 @@ import { LogLineDetailsLinks } from './LogLineDetailsLinks'; import { LogLineDetailsLog } from './LogLineDetailsLog'; import { LogLineDetailsTrace } from './LogLineDetailsTrace'; import { useLogListContext } from './LogListContext'; +import { reportInteractionOnce } from './analytics'; import { getTempoTraceFromLinks } from './links'; import { LogListModel } from './processing'; @@ -124,6 +125,21 @@ export const LogLineDetailsComponent = memo( [fieldsWithLinks.links, fieldsWithLinks.linksFromVariableMap] ); + useEffect(() => { + if (noInteractions) { + return; + } + reportInteractionOnce('logs_log_line_details_fields_displayed', { + links: allLinks.length, + trace: trace !== undefined, + fields: fieldsWithoutLinks.length, + labels: labelsWithLinks.length, + labelGroups: labelGroups.join(', '), + }); + // Once + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return ( <> diff --git a/public/app/features/logs/components/panel/LogLineDetailsHeader.tsx b/public/app/features/logs/components/panel/LogLineDetailsHeader.tsx index 34dd3b3b151..6968c4c138c 100644 --- a/public/app/features/logs/components/panel/LogLineDetailsHeader.tsx +++ b/public/app/features/logs/components/panel/LogLineDetailsHeader.tsx @@ -102,7 +102,11 @@ export const LogLineDetailsHeader = ({ focusLogLine, log, search, onSearch }: Pr } setDetailsMode(newMode); - }, [detailsMode, logOptionsStorageKey, setDetailsMode]); + + reportInteractionWrapper('logs_log_line_details_header_toggle_details_mode', { + newMode, + }); + }, [detailsMode, logOptionsStorageKey, reportInteractionWrapper, setDetailsMode]); const toggleLogLine = useCallback(() => { if (logLineDisplayed) { diff --git a/public/app/features/logs/components/panel/LogListContext.tsx b/public/app/features/logs/components/panel/LogListContext.tsx index 139f40ea98e..e7108af0881 100644 --- a/public/app/features/logs/components/panel/LogListContext.tsx +++ b/public/app/features/logs/components/panel/LogListContext.tsx @@ -34,7 +34,7 @@ import { getDefaultDetailsMode, getDetailsWidth } from './LogDetailsContext'; import { LogLineTimestampResolution } from './LogLine'; import { GetRowContextQueryFn, LogLineMenuCustomItem } from './LogLineMenu'; import { LogListOptions, LogListFontSize } from './LogList'; -import { reportInteractionOnce } from './analytics'; +import { collectInsights } from './analytics'; import { LogListModel } from './processing'; export interface LogListContextData extends Omit { @@ -241,7 +241,7 @@ export const LogListContextProvider = ({ if (noInteractions) { return; } - reportInteractionOnce(`logs_log_list_${app}_logs_displayed`, { + collectInsights(logs, app, { dedupStrategy, fontSize, forceEscape: logListState.forceEscape, diff --git a/public/app/features/logs/components/panel/analytics.ts b/public/app/features/logs/components/panel/analytics.ts index 39ec415b939..54d819debb3 100644 --- a/public/app/features/logs/components/panel/analytics.ts +++ b/public/app/features/logs/components/panel/analytics.ts @@ -1,5 +1,8 @@ +import { CoreApp, LogRowModel } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; +import { identifyOTelLanguages } from '../otel/formats'; + export const reportInteractionOnce = (interactionName: string, properties?: Record) => { const key = `logs.interactions.${interactionName}`; if (sessionStorage.getItem(key)) { @@ -8,3 +11,56 @@ export const reportInteractionOnce = (interactionName: string, properties?: Reco sessionStorage.setItem(key, '1'); reportInteraction(interactionName, properties); }; + +export function collectInsights(logs: LogRowModel[], app: CoreApp, properties?: Record) { + if (!logs.length) { + return; + } + + const { longest, shortest, average, median } = getLogsStats(logs); + + reportInteractionOnce(`logs_log_list_${app}_logs_displayed`, { + ...properties, + otelLanguage: identifyOTelLanguages(logs).join(', '), + ansi: logs.some((logs) => logs.hasAnsi), + unescaped: logs.some((logs) => logs.hasUnescapedContent), + dsType: logs[0]?.datasourceType ?? '', + count: logs.length, + longestLog: longest, + shortestLog: shortest, + averageLog: average, + medianLog: median, + }); +} + +function getLogsStats(logs: LogRowModel[]) { + let longest = 0, + shortest = logs[0].raw.length, + median = 0; + + const lengths: number[] = []; + let sum = 0; + + for (let i = 0; i < logs.length; i++) { + let length = logs[i].raw.length; + if (length > longest) { + longest = length; + } else if (length < shortest) { + shortest = length; + } + sum += length; + lengths.push(length); + } + + lengths.sort((a, b) => a - b); + + const mid = Math.floor(lengths.length / 2); + + if (lengths.length % 2 === 0) { + median = (lengths[mid - 1] + lengths[mid]) / 2; + } else { + median = lengths[mid]; + } + + return { longest, shortest, average: Math.round(sum / logs.length), median }; +} From 015219e49fb9f73bacb2d387ee061950df62d58c Mon Sep 17 00:00:00 2001 From: Oleg Zaytsev Date: Wed, 17 Dec 2025 10:43:50 +0100 Subject: [PATCH 184/297] Logs Panel: Integrate client-side search with Popover Menu (#114653) * Explore: Add custom text highlighting to logs panel Add ability to select text in log lines and highlight all occurrences with persistent colors. Highlights are stored in URL state and cycle through the theme's visualization palette. - Add CustomHighlight type to ExploreLogsPanelState - Implement LogListHighlightContext for state management - Generate custom highlight grammar using Prism.js tokens - Add "Highlight occurrences" option to popover menu - Add "Reset highlights" control when highlights exist - Fix pruneObject to preserve colorIndex: 0 in URL state * Fix CI failures: formatting and i18n extraction - Run prettier on LogLine.tsx - Run i18n-extract to update translation strings * Fix lint errors - Use theme.shape.radius.default instead of literal '2px' in LogLine.tsx - Remove unnecessary type assertion in grammar.ts * Fix TypeScript error in grammar.ts Use Record type for dynamic grammar object to allow string indexing without type assertions. * Replace hardcoded HIGHLIGHT_COLOR_COUNT with actual theme palette length Use useTheme2() hook to dynamically get the palette length instead of hardcoding it to 50. This ensures the color cycling works correctly regardless of the actual theme palette size. * Backtrack to a stable point and revert changes * Implement using search * New translations * LogListSearch: refactor search state * PopoverMenu: add divider * LogLine: remove padding and update border radius * LogListSearch: add missing tooltips * Refactor keybindings * More cleanup * LogListSearch: don't autoscroll with filterLogs --------- Co-authored-by: Matias Chomicki --- .../app/features/explore/Logs/PopoverMenu.tsx | 15 +++++- .../logs/components/panel/LogList.tsx | 10 ++++ .../logs/components/panel/LogListSearch.tsx | 51 +++++++++---------- .../components/panel/LogListSearchContext.tsx | 5 +- .../logs/components/panel/processing.ts | 2 +- .../logs/components/panel/useKeyBindings.ts | 10 ++-- public/locales/en-US/grafana.json | 3 +- 7 files changed, 62 insertions(+), 34 deletions(-) diff --git a/public/app/features/explore/Logs/PopoverMenu.tsx b/public/app/features/explore/Logs/PopoverMenu.tsx index 9c435bee1d7..5d5895ed5c4 100644 --- a/public/app/features/explore/Logs/PopoverMenu.tsx +++ b/public/app/features/explore/Logs/PopoverMenu.tsx @@ -14,6 +14,7 @@ interface PopoverMenuProps { y: number; onClickFilterString?: (value: string, refId?: string) => void; onClickFilterOutString?: (value: string, refId?: string) => void; + onClickSearchString?: (text: string) => void; onDisable: () => void; row: LogRowModel; close: () => void; @@ -24,6 +25,7 @@ export const PopoverMenu = ({ y, onClickFilterString, onClickFilterOutString, + onClickSearchString, selection, row, close, @@ -50,7 +52,7 @@ export const PopoverMenu = ({ props.onDisable(); }, [props, row.datasourceType, selection.length]); - const supported = onClickFilterString || onClickFilterOutString; + const supported = onClickFilterString || onClickFilterOutString || onClickSearchString; if (!supported) { return null; @@ -89,6 +91,17 @@ export const PopoverMenu = ({ /> )} + {onClickSearchString && ( + { + onClickSearchString(selection); + close(); + track('search_text', selection.length, row.datasourceType); + }} + /> + )} + diff --git a/public/app/features/logs/components/panel/LogList.tsx b/public/app/features/logs/components/panel/LogList.tsx index 3c51af0342b..8cfbaee1665 100644 --- a/public/app/features/logs/components/panel/LogList.tsx +++ b/public/app/features/logs/components/panel/LogList.tsx @@ -280,6 +280,7 @@ const LogListComponent = ({ wrapLogMessage, } = useLogListContext(); const { detailsMode, showDetails, toggleDetails } = useLogDetailsContext(); + const { setSearch, showSearch } = useLogListSearchContext(); const [processedLogs, setProcessedLogs] = useState([]); const [listHeight, setListHeight] = useState(getListHeight(containerElement, app)); const theme = useTheme2(); @@ -441,6 +442,14 @@ const LogListComponent = ({ [debouncedScrollToItem, filteredLogs] ); + const onClickSearchString = useCallback( + (search: string) => { + showSearch(); + setSearch(search); + }, + [setSearch, showSearch] + ); + const logLevels = useMemo(() => getLevelsFromLogs(processedLogs), [processedLogs]); if (!containerElement || listHeight == null) { @@ -471,6 +480,7 @@ const LogListComponent = ({ {...popoverState.popoverMenuCoordinates} onClickFilterString={onClickFilterString} onClickFilterOutString={onClickFilterOutString} + onClickSearchString={onClickSearchString} onDisable={onDisablePopoverMenu} /> )} diff --git a/public/app/features/logs/components/panel/LogListSearch.tsx b/public/app/features/logs/components/panel/LogListSearch.tsx index 3cdd1cbbe3d..7cae9f36d5e 100644 --- a/public/app/features/logs/components/panel/LogListSearch.tsx +++ b/public/app/features/logs/components/panel/LogListSearch.tsx @@ -18,19 +18,11 @@ interface Props { export const LOG_LIST_SEARCH_HEIGHT = 48; export const LogListSearch = ({ listRef, logs }: Props) => { - const { - hideSearch, - filterLogs, - matchingUids, - setMatchingUids, - setSearch: setContextSearch, - searchVisible, - toggleFilterLogs, - } = useLogListSearchContext(); + const { hideSearch, filterLogs, matchingUids, search, setMatchingUids, setSearch, searchVisible, toggleFilterLogs } = + useLogListSearchContext(); const { displayedFields, noInteractions } = useLogListContext(); - const [search, setSearch] = useState(''); const [currentResult, setCurrentResult] = useState(null); - const inputRef = useRef(''); + const inputRef = useRef(null); const searchUsedRef = useRef(false); const styles = useStyles2(getStyles); @@ -43,16 +35,15 @@ export const LogListSearch = ({ listRef, logs }: Props) => { const handleChange = useCallback( (e: ChangeEvent) => { - inputRef.current = e.target.value; startTransition(() => { - setSearch(inputRef.current); + setSearch(inputRef.current?.value ?? ''); }); if (!searchUsedRef.current && !noInteractions) { reportInteraction('logs_log_list_search_used'); searchUsedRef.current = true; } }, - [noInteractions] + [noInteractions, setSearch] ); const prevResult = useCallback(() => { @@ -78,19 +69,27 @@ export const LogListSearch = ({ listRef, logs }: Props) => { setCurrentResult(null); return; } - if (!currentResult) { + if (currentResult === null) { setCurrentResult(0); - listRef?.scrollToItem(logs.indexOf(matches[0]), 'center'); + // No need to filter if we're only showing matching logs, otherwise scroll to the first result. + if (!filterLogs) { + listRef?.scrollToItem(logs.indexOf(matches[0]), 'center'); + } } - }, [currentResult, listRef, logs, matches]); + }, [currentResult, filterLogs, listRef, logs, matches]); useEffect(() => { if (!searchVisible) { - setSearch(''); - setContextSearch(undefined); setMatchingUids(null); } - }, [searchVisible, setContextSearch, setMatchingUids]); + }, [searchVisible, setMatchingUids]); + + useEffect(() => { + if (!inputRef.current || !search) { + return; + } + inputRef.current.value = search; + }, [search]); useEffect(() => { const newMatchingUids = matches.map((log) => log.uid); @@ -104,13 +103,12 @@ export const LogListSearch = ({ listRef, logs }: Props) => { .forEach((log) => log.setCurrentSearch(undefined)); } - setContextSearch(search ? search : undefined); if (!sameLogs) { setMatchingUids(newMatchingUids.length ? newMatchingUids : null); } else if (!matches.length) { setMatchingUids(null); } - }, [logs, matches, matchingUids, search, setContextSearch, setMatchingUids]); + }, [logs, matches, matchingUids, search, setMatchingUids]); if (!searchVisible) { return null; @@ -126,6 +124,7 @@ export const LogListSearch = ({ listRef, logs }: Props) => { onChange={handleChange} autoFocus placeholder={t('logs.log-list-search.input-placeholder', 'Search in logs')} + ref={inputRef} suffix={suffix} /> @@ -141,22 +140,22 @@ export const LogListSearch = ({ listRef, logs }: Props) => { onClick={prevResult} disabled={!matches || !matches.length} name="angle-up" - aria-label={t('logs.log-list-search.prev', 'Previous result')} + tooltip={t('logs.log-list-search.prev', 'Previous result')} /> - + ); }; diff --git a/public/app/features/logs/components/panel/LogListSearchContext.tsx b/public/app/features/logs/components/panel/LogListSearchContext.tsx index ff1548a56cf..22be0c251a6 100644 --- a/public/app/features/logs/components/panel/LogListSearchContext.tsx +++ b/public/app/features/logs/components/panel/LogListSearchContext.tsx @@ -7,7 +7,7 @@ export interface LogListSearchContextData { search?: string; searchVisible?: boolean; setMatchingUids: (matches: string[] | null) => void; - setSearch: (search: string | undefined) => void; + setSearch: (search: string) => void; showSearch: () => void; toggleFilterLogs: () => void; } @@ -33,13 +33,14 @@ export const useLogListSearchContext = (): LogListSearchContextData => { }; export const LogListSearchContextProvider = ({ children }: { children: ReactNode }) => { - const [search, setSearch] = useState(undefined); + const [search, setSearch] = useState(''); const [searchVisible, setSearchVisible] = useState(false); const [matchingUids, setMatchingUids] = useState(null); const [filterLogs, setFilterLogs] = useState(false); const hideSearch = useCallback(() => { setSearchVisible(false); + setSearch(''); }, []); const showSearch = useCallback(() => { diff --git a/public/app/features/logs/components/panel/processing.ts b/public/app/features/logs/components/panel/processing.ts index dcb391c8e07..9132c2b7e6e 100644 --- a/public/app/features/logs/components/panel/processing.ts +++ b/public/app/features/logs/components/panel/processing.ts @@ -336,7 +336,7 @@ function countNewLines(log: string, limit = Infinity) { let count = 0; for (let i = 0; i < log.length; ++i) { // No need to iterate further - if (count > Infinity) { + if (count > limit) { return count; } if (log[i] === '\n') { diff --git a/public/app/features/logs/components/panel/useKeyBindings.ts b/public/app/features/logs/components/panel/useKeyBindings.ts index 6a75dcfcf2b..21776b15bb7 100644 --- a/public/app/features/logs/components/panel/useKeyBindings.ts +++ b/public/app/features/logs/components/panel/useKeyBindings.ts @@ -15,7 +15,7 @@ export const useKeyBindings = () => { const { showDetails, detailsMode, closeDetails } = useLogDetailsContext(); useEffect(() => { - function handleToggleSearch(event: KeyboardEvent) { + function handleOpenSearch(event: KeyboardEvent) { const isMac = navigator.userAgent.includes('Mac'); const isFKey = event.key === 'f' || event.key === 'F'; @@ -23,6 +23,8 @@ export const useKeyBindings = () => { showSearch(); return; } + } + function handleClose(event: KeyboardEvent) { if (event.key === 'Escape' && searchVisible) { hideSearch(); } @@ -30,9 +32,11 @@ export const useKeyBindings = () => { closeDetails(); } } - document.addEventListener('keydown', handleToggleSearch); + document.addEventListener('keydown', handleOpenSearch); + document.addEventListener('keyup', handleClose); return () => { - document.removeEventListener('keydown', handleToggleSearch); + document.removeEventListener('keydown', handleOpenSearch); + document.removeEventListener('keyup', handleClose); }; }, [closeDetails, detailsMode, hideSearch, searchVisible, showDetails.length, showSearch]); }; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index f62b26211f6..c5fc2f1d935 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -10181,7 +10181,8 @@ "copy": "Copy selection", "disable-menu": "Disable menu", "line-contains": "Add as line contains filter", - "line-contains-not": "Add as line does not contain filter" + "line-contains-not": "Add as line does not contain filter", + "search-text": "Search in results" }, "show-log-attributes": "Display log attributes for OTel logs", "timestamp-format": "Timestamp resolution", From e4202db28f4771f05a0dd6b600bc739ede739b3a Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:44:06 +0100 Subject: [PATCH 185/297] CI: enable branch cleanup workflow (#115470) enable branch cleanup workflow --- .github/workflows/cleanup-branches.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cleanup-branches.yml b/.github/workflows/cleanup-branches.yml index 7f4a6909e6e..7f359309cca 100644 --- a/.github/workflows/cleanup-branches.yml +++ b/.github/workflows/cleanup-branches.yml @@ -14,5 +14,5 @@ jobs: - uses: actions/checkout@v5 - uses: grafana/shared-workflows/actions/cleanup-branches@cleanup-branches/v0.2.1 with: - dry-run: true + dry-run: false max-date: "1 month ago" From d02b2a35cde254b907665d19ee24f5d50b082bc3 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 17 Dec 2025 13:17:57 +0300 Subject: [PATCH 186/297] Provisioning: Ignore dashboard change warning after save (#115401) --- .../apis/dashboard/dashboard_storage.go | 8 +++- .../apis/provisioning/resources/dualwriter.go | 10 +++++ pkg/services/live/features/dashboard.go | 14 ++++--- pkg/services/live/live.go | 2 +- pkg/storage/unified/apistore/prepare_test.go | 38 +++++++++++++++++++ .../live/dashboard/dashboardWatcher.ts | 14 +++++-- public/app/features/live/dashboard/types.ts | 1 + .../SaveProvisionedDashboardForm.tsx | 4 ++ 8 files changed, 78 insertions(+), 13 deletions(-) diff --git a/pkg/registry/apis/dashboard/dashboard_storage.go b/pkg/registry/apis/dashboard/dashboard_storage.go index 7ab91f8ec66..8bde214129b 100644 --- a/pkg/registry/apis/dashboard/dashboard_storage.go +++ b/pkg/registry/apis/dashboard/dashboard_storage.go @@ -8,6 +8,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana/pkg/apimachinery/utils" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" @@ -33,8 +34,11 @@ func (d dashboardStorageWrapper) Update(ctx context.Context, name string, objInf obj, created, err := d.Storage.Update(ctx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options) if err == nil && ns.OrgID > 0 && d.live != nil { - if err := d.live.DashboardSaved(ns.OrgID, name); err != nil { - logging.FromContext(ctx).Info("live dashboard update failed", "err", err) + m, err := utils.MetaAccessor(obj) + if err == nil { + if err := d.live.DashboardSaved(ns.OrgID, name, m.GetResourceVersion()); err != nil { + logging.FromContext(ctx).Info("live dashboard update failed", "err", err) + } } } return obj, created, err diff --git a/pkg/registry/apis/provisioning/resources/dualwriter.go b/pkg/registry/apis/provisioning/resources/dualwriter.go index 7c9005a8dd5..9180ace494d 100644 --- a/pkg/registry/apis/provisioning/resources/dualwriter.go +++ b/pkg/registry/apis/provisioning/resources/dualwriter.go @@ -277,6 +277,16 @@ func (r *DualReadWriter) createOrUpdate(ctx context.Context, create bool, opts D // FIXME: to make sure if behaves in the same way as in sync, we should // we should refactor the code to use the same function. if r.shouldUpdateGrafanaDB(opts, parsed) { + // HACK: Get the has from repository -- this will avoid an additional RV increment + // we should change the signature of Create and Update to return FileInfo instead + info, _ = r.repo.Read(ctx, opts.Path, opts.Ref) + if info != nil { + parsed.Meta.SetSourceProperties(utils.SourceProperties{ + Path: opts.Path, + Checksum: info.Hash, + }) + } + if _, err := r.folders.EnsureFolderPathExist(ctx, opts.Path); err != nil { return nil, fmt.Errorf("ensure folder path exists: %w", err) } diff --git a/pkg/services/live/features/dashboard.go b/pkg/services/live/features/dashboard.go index 537042d2da0..bb51635ef3d 100644 --- a/pkg/services/live/features/dashboard.go +++ b/pkg/services/live/features/dashboard.go @@ -26,9 +26,10 @@ const ( // DashboardEvent events related to dashboards type dashboardEvent struct { - UID string `json:"uid"` - Action actionType `json:"action"` // saved, editing, deleted - SessionID string `json:"sessionId,omitempty"` + UID string `json:"uid"` + Action actionType `json:"action"` // saved, editing, deleted + SessionID string `json:"sessionId,omitempty"` + ResourceVersion string `json:"rv,omitempty"` } // DashboardHandler manages all the `grafana/dashboard/*` channels @@ -105,10 +106,11 @@ func (h *DashboardHandler) publish(orgID int64, event dashboardEvent) error { } // DashboardSaved will broadcast to all connected dashboards -func (h *DashboardHandler) DashboardSaved(orgID int64, uid string) error { +func (h *DashboardHandler) DashboardSaved(orgID int64, uid string, rv string) error { return h.publish(orgID, dashboardEvent{ - UID: uid, - Action: ActionSaved, + UID: uid, + Action: ActionSaved, + ResourceVersion: rv, }) } diff --git a/pkg/services/live/live.go b/pkg/services/live/live.go index 7dbca506e2c..fe1dffba1b8 100644 --- a/pkg/services/live/live.go +++ b/pkg/services/live/live.go @@ -482,7 +482,7 @@ type GrafanaLive struct { // DashboardActivityChannel is a service to advertise dashboard activity type DashboardActivityChannel interface { // Called when a dashboard is saved - DashboardSaved(orgID int64, uid string) error + DashboardSaved(orgID int64, uid string, rv string) error // Called when a dashboard is deleted DashboardDeleted(orgID int64, uid string) error diff --git a/pkg/storage/unified/apistore/prepare_test.go b/pkg/storage/unified/apistore/prepare_test.go index 0b36f71117a..a35c2398736 100644 --- a/pkg/storage/unified/apistore/prepare_test.go +++ b/pkg/storage/unified/apistore/prepare_test.go @@ -2,6 +2,7 @@ package apistore import ( "context" + "encoding/json" "math/rand/v2" "strings" "testing" @@ -19,6 +20,7 @@ import ( authlib "github.com/grafana/authlib/types" dashv1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" ) @@ -195,6 +197,42 @@ func TestPrepareObjectForStorage(t *testing.T) { require.Equal(t, int64(2), meta2.GetGeneration()) }) + t.Run("Update should skip incrementing generation when content is unchanged", func(t *testing.T) { + dashboard := dashv1.Dashboard{ + ObjectMeta: v1.ObjectMeta{ + Name: "test", + Generation: 123, + Annotations: map[string]string{ + "A": "B", + utils.AnnoKeyUpdatedTimestamp: "2025-12-17T01:01:00Z", + }, + UID: "XXX", + }, + Spec: v0alpha1.Unstructured{ + Object: map[string]any{ + "hello": "world", + }, + }, + } + dashboard.Name = "test-name" + obj := dashboard.DeepCopyObject() + tmp, err := utils.MetaAccessor(obj) + tmp.SetGeneration(2) + tmp.SetUpdatedTimestampMillis(12345) + require.NoError(t, err) + + v, err := s.prepareObjectForUpdate(ctx, obj, &dashboard) + require.NoError(t, err) + require.False(t, v.hasChanged, "no changes") + + out := &unstructured.Unstructured{} + err = json.Unmarshal(v.raw.Bytes(), out) + require.NoError(t, err) + + require.Equal(t, int64(123), tmp.GetGeneration()) + require.Equal(t, "2025-12-17T01:01:00Z", tmp.GetAnnotation(utils.AnnoKeyUpdatedTimestamp)) + }) + s.opts.RequireDeprecatedInternalID = true t.Run("Should generate internal id", func(t *testing.T) { dashboard := dashv1.Dashboard{} diff --git a/public/app/features/live/dashboard/dashboardWatcher.ts b/public/app/features/live/dashboard/dashboardWatcher.ts index 086147e67e7..2bd13ee00d7 100644 --- a/public/app/features/live/dashboard/dashboardWatcher.ts +++ b/public/app/features/live/dashboard/dashboardWatcher.ts @@ -25,9 +25,11 @@ import { DashboardEvent, DashboardEventAction } from './types'; const sessionId = uuidv4(); class DashboardWatcher { + private static readonly IGNORE_SAVE_WINDOW_MS = 5000; + channel?: LiveChannelAddress; // path to the channel uid?: string; - ignoreSave?: boolean; + ignoreSave = 0; // save any events until this time passes editing = false; lastEditing?: DashboardEvent; subscription?: Unsubscribable; @@ -84,8 +86,9 @@ class DashboardWatcher { this.uid = undefined; } + // ignore the next 5 seconds of save events ignoreNextSave() { - this.ignoreSave = true; + this.ignoreSave = Date.now() + DashboardWatcher.IGNORE_SAVE_WINDOW_MS; } getRecentEditingEvent() { @@ -115,8 +118,11 @@ class DashboardWatcher { case DashboardEventAction.EditingStarted: case DashboardEventAction.Saved: { if (this.ignoreSave) { - this.ignoreSave = false; - return; + if (this.ignoreSave < Date.now()) { + this.ignoreSave = 0; // process the event + } else { + return; + } } const dash = getDashboardSrv().getCurrent(); diff --git a/public/app/features/live/dashboard/types.ts b/public/app/features/live/dashboard/types.ts index cffe686fc27..a5954211dfa 100644 --- a/public/app/features/live/dashboard/types.ts +++ b/public/app/features/live/dashboard/types.ts @@ -11,4 +11,5 @@ export interface DashboardEvent { message?: string; sessionId?: string; timestamp?: number; + rv?: string; } diff --git a/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx b/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx index 219bb521111..a2df43a1f95 100644 --- a/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx +++ b/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx @@ -12,6 +12,7 @@ import kbn from 'app/core/utils/kbn'; import { Resource } from 'app/features/apiserver/types'; import { SaveDashboardFormCommonOptions } from 'app/features/dashboard-scene/saving/SaveDashboardForm'; import { getDashboardUrl } from 'app/features/dashboard-scene/utils/getDashboardUrl'; +import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; import { validationSrv } from 'app/features/manage-dashboards/services/ValidationSrv'; import { PROVISIONING_URL } from 'app/features/provisioning/constants'; import { useCreateOrUpdateRepositoryFile } from 'app/features/provisioning/hooks/useCreateOrUpdateRepositoryFile'; @@ -204,6 +205,9 @@ export function SaveProvisionedDashboardForm({ repositoryType: repository?.type ?? 'unknown', }); + // ignore incoming save events + dashboardWatcher.ignoreNextSave(); + createOrUpdateFile({ // Skip adding ref to the default branch request ref: ref === repository?.branch ? undefined : ref, From fe49ae05c0fd1389e577119925ada87e8fbbf98b Mon Sep 17 00:00:00 2001 From: Victor Cinaglia Date: Wed, 17 Dec 2025 09:03:29 -0300 Subject: [PATCH 187/297] Auth: Disable login prompt option for Google OAuth when "use_refresh_token" is enabled (#115367) * Auth: Google OAuth consent prompt takes precedence when use_refresh_token is true * Auth: Disable login prompt option for Google OAuth when use_refresh_token is true * yarn run prettier:check --write * feedback: validate login prompt when use_refresh_token is true --- pkg/login/social/connectors/google_oauth.go | 10 +- .../social/connectors/google_oauth_test.go | 142 +++++++++++++++++- pkg/login/social/connectors/social_base.go | 6 +- .../features/auth-config/FieldRenderer.tsx | 28 +++- public/app/features/auth-config/fields.tsx | 37 +++-- public/app/features/auth-config/types.ts | 17 ++- public/locales/en-US/grafana.json | 2 + 7 files changed, 222 insertions(+), 20 deletions(-) diff --git a/pkg/login/social/connectors/google_oauth.go b/pkg/login/social/connectors/google_oauth.go index 81c7cd31f9d..b94f0514879 100644 --- a/pkg/login/social/connectors/google_oauth.go +++ b/pkg/login/social/connectors/google_oauth.go @@ -81,7 +81,15 @@ func (s *SocialGoogle) Validate(ctx context.Context, newSettings ssoModels.SSOSe return validation.Validate(info, requester, validation.MustBeEmptyValidator(info.AuthUrl, "Auth URL"), validation.MustBeEmptyValidator(info.TokenUrl, "Token URL"), - validation.MustBeEmptyValidator(info.ApiUrl, "API URL")) + validation.MustBeEmptyValidator(info.ApiUrl, "API URL"), + loginPromptValidator) +} + +func loginPromptValidator(info *social.OAuthInfo, requester identity.Requester) error { + if info.UseRefreshToken && !slices.Contains([]string{"", "consent"}, info.LoginPrompt) { + return ssosettings.ErrInvalidOAuthConfig("If provided, login_prompt must be set to consent when use_refresh_token is enabled.") + } + return nil } func (s *SocialGoogle) Reload(ctx context.Context, settings ssoModels.SSOSettings) error { diff --git a/pkg/login/social/connectors/google_oauth_test.go b/pkg/login/social/connectors/google_oauth_test.go index d330c39d78d..620c448013b 100644 --- a/pkg/login/social/connectors/google_oauth_test.go +++ b/pkg/login/social/connectors/google_oauth_test.go @@ -9,6 +9,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "net/url" "testing" "time" @@ -18,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/oauth2" + "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -871,6 +873,39 @@ func TestSocialGoogle_Validate(t *testing.T) { }, wantErr: ssosettings.ErrBaseInvalidOAuthConfig, }, + { + name: "fails if use_refresh_token is enabled and login prompt is neither empty or 'consent'", + settings: ssoModels.SSOSettings{ + Settings: map[string]any{ + "client_id": "client-id", + "use_refresh_token": "true", + "login_prompt": "login", + }, + }, + wantErr: ssosettings.ErrBaseInvalidOAuthConfig, + }, + { + name: "succeeds if use_refresh_token is enabled and login prompt is empty", + settings: ssoModels.SSOSettings{ + Settings: map[string]any{ + "client_id": "client-id", + "use_refresh_token": "true", + "login_prompt": "", + }, + }, + wantErr: nil, + }, + { + name: "succeeds if use_refresh_token is enabled and login prompt is consent", + settings: ssoModels.SSOSettings{ + Settings: map[string]any{ + "client_id": "client-id", + "use_refresh_token": "true", + "login_prompt": "consent", + }, + }, + wantErr: nil, + }, } for _, tc := range testCases { @@ -886,7 +921,13 @@ func TestSocialGoogle_Validate(t *testing.T) { require.ErrorIs(t, err, tc.wantErr) return } - require.NoError(t, err) + + if err != nil { + var e errutil.Error + require.True(t, errors.As(err, &e)) + require.NoError(t, e, "expected no error, got %v", e.PublicMessage) + return + } }) } } @@ -1024,3 +1065,102 @@ func TestIsHDAllowed(t *testing.T) { }) } } + +func TestSocialGoogle_AuthCodeURL(t *testing.T) { + testCases := []struct { + name string + info *social.OAuthInfo + opts []oauth2.AuthCodeOption + state string + wantURL *url.URL + }{ + { + name: "should return the correct auth code URL", + info: &social.OAuthInfo{ + ClientId: "client-id", + ClientSecret: "client-secret", + AuthUrl: "https://example.com/auth", + LoginPrompt: "login", + Scopes: []string{"openid", "email", "profile"}, + }, + state: "test-state", + opts: []oauth2.AuthCodeOption{ + oauth2.SetAuthURLParam("extra_param", "extra_value"), + }, + wantURL: &url.URL{ + Scheme: "https", + Host: "example.com", + Path: "/auth", + RawQuery: url.Values{ + "state": {"test-state"}, + "prompt": {"login"}, + "response_type": {"code"}, + "client_id": {"client-id"}, + "redirect_uri": {"/login/google"}, + "scope": {"openid email profile"}, + "extra_param": {"extra_value"}, + }.Encode(), + }, + }, + { + name: "should add access type offline and approval force if use refresh token is enabled", + info: &social.OAuthInfo{ + ClientId: "client-id", + ClientSecret: "client-secret", + AuthUrl: "https://example.com/auth", + Scopes: []string{"openid", "email", "profile"}, + UseRefreshToken: true, + }, + state: "test-state", + wantURL: &url.URL{ + Scheme: "https", + Host: "example.com", + Path: "/auth", + RawQuery: url.Values{ + "state": {"test-state"}, + "prompt": {"consent"}, + "response_type": {"code"}, + "client_id": {"client-id"}, + "redirect_uri": {"/login/google"}, + "scope": {"openid email profile"}, + "access_type": {"offline"}, + }.Encode(), + }, + }, + { + name: "should override configured login prompt if use refresh token is enabled", + info: &social.OAuthInfo{ + ClientId: "client-id", + ClientSecret: "client-secret", + AuthUrl: "https://example.com/auth", + Scopes: []string{"openid", "email", "profile"}, + UseRefreshToken: true, + }, + state: "test-state", + wantURL: &url.URL{ + Scheme: "https", + Host: "example.com", + Path: "/auth", + RawQuery: url.Values{ + "state": {"test-state"}, + "prompt": {"consent"}, + "response_type": {"code"}, + "client_id": {"client-id"}, + "redirect_uri": {"/login/google"}, + "scope": {"openid email profile"}, + "access_type": {"offline"}, + }.Encode(), + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + s := NewGoogleProvider(tc.info, &setting.Cfg{}, nil, ssosettingstests.NewFakeService(), featuremgmt.WithFeatures()) + gotURL := s.AuthCodeURL(tc.state, tc.opts...) + parsedURL, err := url.Parse(gotURL) + require.NoError(t, err) + require.EqualValues(t, tc.wantURL, parsedURL) + }) + } +} diff --git a/pkg/login/social/connectors/social_base.go b/pkg/login/social/connectors/social_base.go index 0db4d81baeb..3ecbf8e334e 100644 --- a/pkg/login/social/connectors/social_base.go +++ b/pkg/login/social/connectors/social_base.go @@ -91,7 +91,11 @@ func (s *SocialBase) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) st func (s *SocialBase) getAuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string { if s.info.LoginPrompt != "" { promptOpt := oauth2.SetAuthURLParam("prompt", s.info.LoginPrompt) - opts = append(opts, promptOpt) + + // Prepend the prompt option to the opts slice to ensure it is applied last. + // This is necessary in case the caller provides an option that overrides the prompt, + // such as `oauth2.ApprovalForce`. + opts = append([]oauth2.AuthCodeOption{promptOpt}, opts...) } return s.Config.AuthCodeURL(state, opts...) diff --git a/public/app/features/auth-config/FieldRenderer.tsx b/public/app/features/auth-config/FieldRenderer.tsx index 1d80f835fb0..687079f1281 100644 --- a/public/app/features/auth-config/FieldRenderer.tsx +++ b/public/app/features/auth-config/FieldRenderer.tsx @@ -35,17 +35,23 @@ export const FieldRenderer = ({ const [isSecretConfigured, setIsSecretConfigured] = useState(secretConfigured); const isDependantField = typeof field !== 'string'; const name = isDependantField ? field.name : field; - const parentValue = isDependantField ? watch(field.dependsOn) : null; + const parentValue = isDependantField && field.dependsOn ? watch(field.dependsOn) : null; const fieldData = fieldMap(provider)[name]; const theme = useTheme2(); + + // Handle disabledWhen configuration + const disabledWhen = isDependantField ? field.disabledWhen : undefined; + const disabledWhenValue = disabledWhen ? watch(disabledWhen.field) : undefined; + const isDisabled = disabledWhen ? disabledWhenValue === disabledWhen.is : false; + // Unregister a field that depends on a toggle to clear its data useEffect(() => { - if (isDependantField) { + if (isDependantField && field.dependsOn) { if (!parentValue) { unregister(name); } } - }, [unregister, name, parentValue, isDependantField]); + }, [unregister, name, parentValue, isDependantField, field]); const isNotEmptySelectableValueArray = ( current: string | boolean | Record | Array> | undefined @@ -64,6 +70,13 @@ export const FieldRenderer = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // Set the value when the field is disabled + useEffect(() => { + if (isDisabled && disabledWhen?.disabledValue) { + setValue(name, disabledWhen.disabledValue.value); + } + }, [isDisabled, disabledWhen?.disabledValue, name, setValue]); + if (!field) { console.log('missing field:', name); return null; @@ -74,12 +87,12 @@ export const FieldRenderer = ({ } // Dependant field means the field depends on another field's value and shouldn't be rendered if the parent field is false - if (isDependantField) { - const parentValue = watch(field.dependsOn); + if (isDependantField && field.dependsOn) { if (!parentValue) { return null; } } + const fieldProps = { label: fieldData.label, required: !!fieldData.validation?.required, @@ -131,10 +144,10 @@ export const FieldRenderer = ({ rules={fieldData.validation} name={name} control={control} - render={({ field: { ref, onChange, ...fieldProps }, fieldState: { invalid } }) => { + render={({ field: { ref, onChange, ...controllerFieldProps }, fieldState: { invalid } }) => { return (
diff --git a/scripts/cli/tsconfig.json b/scripts/cli/tsconfig.json index 468d9938751..057344a363c 100644 --- a/scripts/cli/tsconfig.json +++ b/scripts/cli/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "moduleResolution": "node", - "module": "commonjs" + "moduleResolution": "nodenext", + "module": "NodeNext" }, "extends": "../../tsconfig.json", "ts-node": { diff --git a/scripts/prepare-npm-package.js b/scripts/prepare-npm-package.js index 054b3d12609..7a11a2eca4a 100644 --- a/scripts/prepare-npm-package.js +++ b/scripts/prepare-npm-package.js @@ -1,108 +1,35 @@ +//@ts-check import PackageJson from '@npmcli/package-json'; -import { mkdir } from 'node:fs/promises'; const cwd = process.cwd(); try { const pkgJson = await PackageJson.load(cwd); - const cjsIndex = pkgJson.content.publishConfig?.main ?? pkgJson.content.main; - const esmIndex = pkgJson.content.publishConfig?.module ?? pkgJson.content.module; - const typesIndex = pkgJson.content.publishConfig?.types ?? pkgJson.content.types; + const pkgJsonExports = pkgJson.content.exports; - const exports = { - './package.json': './package.json', - '.': { - import: { - types: typesIndex, - default: esmIndex, - }, - require: { - types: typesIndex, - default: cjsIndex, - }, - }, - }; - // Fix so scenes can access `@grafana/schema` nested dist import paths e.g. - // import {} from '@grafana/schema/dist/esm/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen' - if (pkgJson.content.name === '@grafana/schema') { - exports['./dist/*'] = { - types: './dist/*', - default: './dist/*', - }; - } - - // Fix for @grafana/i18n so eslint-plugin can be imported by consumers - if (pkgJson.content.name === '@grafana/i18n') { - exports['./eslint-plugin'] = { - types: './dist/eslint/index.d.ts', - import: './dist/eslint/index.cjs', - require: './dist/eslint/index.cjs', - }; - } - - pkgJson.update({ - main: cjsIndex, - types: typesIndex, - module: esmIndex, - exports, - }); - - await pkgJson.save(); - - // If an alias package name is provided we add an exports entry for the alias - // then generate an additional "nested" package.json for typescript resolution that - // doesn't use the exports property in package.json. - if (process.env.ALIAS_PACKAGE_NAME) { - const aliasNames = process.env.ALIAS_PACKAGE_NAME.split(','); - - const additionalExports = aliasNames.reduce((acc, alias) => { - acc[`./${alias}`] = { - import: { - types: typesIndex.replace('index', alias), - default: esmIndex.replace('index', alias), - }, - require: { - types: typesIndex.replace('index', alias), - default: cjsIndex.replace('index', alias), - }, - }; - return acc; - }, {}); + // skip packages without exports otherwise consumers cannot import anything from the package + if (pkgJsonExports && typeof pkgJsonExports === 'object') { + // Remove all exports that only contain a single key '@grafana-app/source' + // as these will not resolve when validating packages with attw because the + // source code is not available in the tarball. + for (const [key, val] of Object.entries(pkgJsonExports)) { + if ( + val !== null && + typeof val === 'object' && + Object.keys(val).length === 1 && + Object.keys(val)[0] === '@grafana-app/source' + ) { + delete pkgJsonExports[key]; + } + } pkgJson.update({ - exports: { - ...pkgJson.content.exports, - ...additionalExports, - }, - files: [...pkgJson.content.files, ...aliasNames], + exports: pkgJsonExports, }); - await pkgJson.save(); - for await (const aliasName of aliasNames) { - await createAliasPackageJsonFiles(pkgJson.content, aliasName); - } + await pkgJson.save(); } } catch (e) { console.error(e); process.exit(1); } - -async function createAliasPackageJsonFiles(packageJsonContent, aliasName) { - const pkgName = `${packageJsonContent.name}/${aliasName}`; - try { - console.log(`📦 Writing alias package.json for ${pkgName}.`); - const pkgJsonPath = `${cwd}/${aliasName}`; - await mkdir(pkgJsonPath, { recursive: true }); - const pkgJson = await PackageJson.create(pkgJsonPath, { - data: { - name: pkgName, - types: `../dist/types/${aliasName}.d.ts`, - main: `../dist/cjs/${aliasName}.cjs`, - module: `../dist/esm/${aliasName}.mjs`, - }, - }); - await pkgJson.save(); - } catch (error) { - throw new Error(`Error generating package.json for ${pkgName}`, error); - } -} diff --git a/scripts/tsconfig.base.json b/scripts/tsconfig.base.json index 55eacdf4b61..152674b91e7 100644 --- a/scripts/tsconfig.base.json +++ b/scripts/tsconfig.base.json @@ -4,6 +4,7 @@ "alwaysStrict": true, "strict": true, "allowSyntheticDefaultImports": true, + "customConditions": ["@grafana-app/source"], "downlevelIteration": true, "esModuleInterop": true, "experimentalDecorators": true, diff --git a/scripts/validate-npm-packages.sh b/scripts/validate-npm-packages.sh index 92e871b6899..99474041107 100755 --- a/scripts/validate-npm-packages.sh +++ b/scripts/validate-npm-packages.sh @@ -17,7 +17,7 @@ for file in "$ARTIFACTS_DIR"/*.tgz; do fi # shellcheck disable=SC2086 - if ! yarn attw "$file" --ignore-rules "false-cjs" $ATTW_FLAGS; then + if ! NODE_OPTIONS="-C @grafana-app/source" yarn attw "$file" --ignore-rules "false-cjs" $ATTW_FLAGS; then echo "attw check failed for $file" echo "" failed_checks+=("$file - yarn attw") diff --git a/scripts/webpack/webpack.common.js b/scripts/webpack/webpack.common.js index e694b812100..f81c279d6a3 100644 --- a/scripts/webpack/webpack.common.js +++ b/scripts/webpack/webpack.common.js @@ -22,6 +22,7 @@ module.exports = { publicPath: 'public/build/', }, resolve: { + conditionNames: ['@grafana-app/source', '...'], extensions: ['.ts', '.tsx', '.es6', '.js', '.json', '.svg'], alias: { // some of data source plugins use global Prism object to add the language definition diff --git a/tsconfig.json b/tsconfig.json index 963aa84555c..46f7b630cb2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,9 +18,10 @@ } }, "ts-node": { + "swc": true, "compilerOptions": { "module": "ESNext", - "moduleResolution": "Node" + "moduleResolution": "Bundler" } }, "include": [ diff --git a/yarn.lock b/yarn.lock index 1c4d07dbb6e..e20676b04f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8290,6 +8290,30 @@ __metadata: languageName: node linkType: hard +"@storybook/core@patch:@storybook/core@npm%3A8.6.2#~/.yarn/patches/@storybook-core-npm-8.6.2-8c752112c0.patch": + version: 8.6.2 + resolution: "@storybook/core@patch:@storybook/core@npm%3A8.6.2#~/.yarn/patches/@storybook-core-npm-8.6.2-8c752112c0.patch::version=8.6.2&hash=f4cc1f" + dependencies: + "@storybook/theming": "npm:8.6.2" + better-opn: "npm:^3.0.2" + browser-assert: "npm:^1.2.1" + esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0" + esbuild-register: "npm:^3.5.0" + jsdoc-type-pratt-parser: "npm:^4.0.0" + process: "npm:^0.11.10" + recast: "npm:^0.23.5" + semver: "npm:^7.6.2" + util: "npm:^0.12.5" + ws: "npm:^8.2.3" + peerDependencies: + prettier: ^2 || ^3 + peerDependenciesMeta: + prettier: + optional: true + checksum: 10/cd95a51437135dd3c4333b14acefd528d8064b2cea7789f859ba80783c115c92ed4be51d4a7bd6236888fdd5f46f488a379e0c71bc1a712ffe6dc1353fb4e648 + languageName: node + linkType: hard + "@storybook/csf-plugin@npm:8.6.2": version: 8.6.2 resolution: "@storybook/csf-plugin@npm:8.6.2" From e29bb47e95006886b512792901a21c2e60536d2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Jim=C3=A9nez=20S=C3=A1nchez?= Date: Thu, 18 Dec 2025 13:16:57 +0100 Subject: [PATCH 218/297] Provisioning: Add Git Sync limitations warning and migrate resources checkbox (#115532) * Provisioning: Add Git Sync limitations warning and migrate resources checkbox - Update SynchronizeStep alert to use warning severity with comprehensive Git Sync limitations - Add conditional warnings for instance sync (permissions loss, alerts/library panels loss) - Add conditional warnings for folder sync (folder structure changes, manual cleanup needed) - Add "Migrate existing resources" checkbox for folder sync mode - Update useCreateSyncJob hook to handle migrateResources option for folder sync - Extract i18n translations for new strings * Simplify createSyncJob: calculate requiresMigration in caller - Remove syncTarget and migrateResources parameters from useCreateSyncJob hook - Calculate requiresMigration in SynchronizeStep based on sync target and checkbox value - Pass requiresMigration as parameter to createSyncJob function * Revert: Pass requiresMigration as hook parameter - Calculate requiresMigration in SynchronizeStep using useMemo - Pass requiresMigration to useCreateSyncJob hook - Remove parameter from createSyncJob function call * Revert "Revert: Pass requiresMigration as hook parameter" This reverts commit 97e3b7107d282d10a1be487d9186daaaf62f41aa. * Fix TypeScript errors in ProvisioningWizard - Remove requiresMigration from useCreateSyncJob call - Pass requiresMigration parameter to createSyncJob call - Remove unused Target import from SynchronizeStep * Show migrate resources checkbox for instance sync (checked and disabled) - Display checkbox for both instance and folder sync - For instance sync: checkbox is checked and disabled with explanation - For instance sync: automatically set migrateResources to true via useEffect - Update description to explain instance sync requires all resources to be managed * Extract i18n translations for instance-migrate-resources-description * Rename 'Synchronization options' to 'Options' * Update i18n translations: rename synchronization-options to options * Remove unnecessary conditional check for sync target * Add bodySmall variant to announcement banner TextLink * Move requiresMigration calculation logic into useResourceStats hook - Add migrateResources parameter to useResourceStats hook - Calculate final requiresMigration in hook based on sync target and checkbox value - Use watch instead of getValues to reactively get migrateResources value - Simplify startSynchronization to use requiresMigration from hook --- .../Wizard/ProvisioningWizard.tsx | 11 +- .../provisioning/Wizard/SynchronizeStep.tsx | 131 ++++++++++++++---- .../Wizard/hooks/useCreateSyncJob.ts | 5 +- .../Wizard/hooks/useResourceStats.ts | 19 ++- .../app/features/provisioning/Wizard/types.ts | 1 + public/locales/en-US/grafana.json | 13 +- 6 files changed, 135 insertions(+), 45 deletions(-) diff --git a/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx b/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx index 32829dba4c0..0950500a378 100644 --- a/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx +++ b/public/app/features/provisioning/Wizard/ProvisioningWizard.tsx @@ -117,14 +117,9 @@ export const ProvisioningWizard = memo(function ProvisioningWizard({ const [repoName = '', repoType, syncTarget] = watch(['repositoryName', 'repository.type', 'repository.sync.target']); const [submitData] = useCreateOrUpdateRepository(repoName); const [deleteRepository] = useDeleteRepositoryMutation(); - const { - shouldSkipSync, - requiresMigration, - isLoading: isResourceStatsLoading, - } = useResourceStats(repoName, syncTarget); + const { shouldSkipSync, isLoading: isResourceStatsLoading } = useResourceStats(repoName, syncTarget); const { createSyncJob, isLoading: isCreatingSkipJob } = useCreateSyncJob({ repoName: repoName, - requiresMigration, setStepStatusInfo, }); @@ -274,8 +269,8 @@ export const ProvisioningWizard = memo(function ProvisioningWizard({ if (activeStep === 'bootstrap' && canSkipSync) { nextStepIndex = currentStepIndex + 2; // Skip to finish step - // Create a pull job to initialize the repository - const job = await createSyncJob(); + // No migration needed when skipping sync + const job = await createSyncJob(false); if (!job) { return; // Don't proceed if job creation fails } diff --git a/public/app/features/provisioning/Wizard/SynchronizeStep.tsx b/public/app/features/provisioning/Wizard/SynchronizeStep.tsx index 5842b313060..299fe775a0e 100644 --- a/public/app/features/provisioning/Wizard/SynchronizeStep.tsx +++ b/public/app/features/provisioning/Wizard/SynchronizeStep.tsx @@ -3,7 +3,7 @@ import { memo, useEffect, useState } from 'react'; import { useFormContext } from 'react-hook-form'; import { Trans, t } from '@grafana/i18n'; -import { Alert, Button, Field, Spinner, Stack, Text, TextLink } from '@grafana/ui'; +import { Alert, Button, Checkbox, Field, Spinner, Stack, Text, TextLink } from '@grafana/ui'; import { Job, useGetRepositoryStatusQuery } from 'app/api/clients/provisioning/v0alpha1'; import { JobStatus } from '../Job/JobStatus'; @@ -20,13 +20,17 @@ export interface SynchronizeStepProps { } export const SynchronizeStep = memo(function SynchronizeStep({ onCancel, isCancelling }: SynchronizeStepProps) { - const { watch } = useFormContext(); + const { watch, register } = useFormContext(); const { setStepStatusInfo } = useStepStatus(); - const [repoName = '', syncTarget] = watch(['repositoryName', 'repository.sync.target']); - const { requiresMigration } = useResourceStats(repoName, syncTarget); + const [repoName = '', syncTarget, migrateResources] = watch([ + 'repositoryName', + 'repository.sync.target', + 'migrate.migrateResources', + ]); + const { requiresMigration } = useResourceStats(repoName, syncTarget, migrateResources); + const { createSyncJob } = useCreateSyncJob({ repoName, - requiresMigration, setStepStatusInfo, }); const [job, setJob] = useState(); @@ -63,7 +67,7 @@ export const SynchronizeStep = memo(function SynchronizeStep({ onCancel, isCance const isButtonDisabled = hasError || (checked !== undefined && isRepositoryHealthy === false) || healthStatusNotReady; const startSynchronization = async () => { - const response = await createSyncJob(); + const response = await createSyncJob(requiresMigration); if (response) { setJob(response); } @@ -108,41 +112,108 @@ export const SynchronizeStep = memo(function SynchronizeStep({ onCancel, isCance )} {isRepositoryHealthy && ( -
    -
  • - - Resources can still be created, edited, or deleted during this process, but changes may not be exported. + + + + Please be aware of the following limitations. For more details, see the{' '} + + Git Sync documentation + + . -
  • -
  • - - Once provisioning is complete, resources will be marked as managed through external storage. - -
  • -
  • - - The duration of this process depends on the number of resources involved. - -
  • -
  • + +
      +
    • + + Resources can still be created, edited, or deleted during this process, but changes may not be + exported. + +
    • +
    • + + Alerts and library panels are not supported in provisioned folders. + +
    • +
    • + + Fine-grained permissions are not supported. Default permissions apply: Admin, Editor, and Viewer roles + are preserved with their standard access levels. + +
    • +
    • + + The duration of this process depends on the number of resources involved. + +
    • + {syncTarget === 'instance' && ( +
    • + + Existing alerts and library panels will be lost and will not be usable after migration. + +
    • + )} + {syncTarget === 'folder' && ( + <> +
    • + + When migrating existing dashboards, the folder structure will be replicated in the repository. + Original folders will be emptied of dashboards but may still contain alerts or library panels. + +
    • +
    • + + You may need to manually remove or manage original folders after migration. + +
    • + + )} +
    + Enterprise instance administrators can display an announcement banner to notify users that migration is in progress. See{' '} - + this guide {' '} for step-by-step instructions. -
  • -
+ +
)} + + Options + + + + Instance sync requires all resources to be managed. Existing resources will be migrated automatically. + + ) : ( + + Import existing dashboards from all folders into the new provisioned folder + + ) + } + /> + {healthStatusNotReady ? ( <> diff --git a/public/app/features/provisioning/Wizard/hooks/useCreateSyncJob.ts b/public/app/features/provisioning/Wizard/hooks/useCreateSyncJob.ts index 7e3c0c30874..1aaa7a250f0 100644 --- a/public/app/features/provisioning/Wizard/hooks/useCreateSyncJob.ts +++ b/public/app/features/provisioning/Wizard/hooks/useCreateSyncJob.ts @@ -5,14 +5,13 @@ import { StepStatusInfo } from '../types'; export interface UseCreateSyncJobParams { repoName: string; - requiresMigration: boolean; setStepStatusInfo?: (info: StepStatusInfo) => void; } -export function useCreateSyncJob({ repoName, requiresMigration, setStepStatusInfo }: UseCreateSyncJobParams) { +export function useCreateSyncJob({ repoName, setStepStatusInfo }: UseCreateSyncJobParams) { const [createJob, { isLoading }] = useCreateRepositoryJobsMutation(); - const createSyncJob = async () => { + const createSyncJob = async (requiresMigration: boolean) => { if (!repoName) { setStepStatusInfo?.({ status: 'error', diff --git a/public/app/features/provisioning/Wizard/hooks/useResourceStats.ts b/public/app/features/provisioning/Wizard/hooks/useResourceStats.ts index 7e2ffef178b..dd4b222cb19 100644 --- a/public/app/features/provisioning/Wizard/hooks/useResourceStats.ts +++ b/public/app/features/provisioning/Wizard/hooks/useResourceStats.ts @@ -100,7 +100,7 @@ function getResourceStats(files?: GetRepositoryFilesApiResponse, stats?: GetReso /** * Hook that provides resource statistics and sync logic */ -export function useResourceStats(repoName?: string, syncTarget?: RepositoryView['target']) { +export function useResourceStats(repoName?: string, syncTarget?: RepositoryView['target'], migrateResources?: boolean) { const resourceStatsQuery = useGetResourceStatsQuery(repoName ? undefined : skipToken); const filesQuery = useGetRepositoryFilesQuery(repoName ? { name: repoName } : skipToken); @@ -121,7 +121,22 @@ export function useResourceStats(repoName?: string, syncTarget?: RepositoryView[ }; }, [resourceStatsQuery.data]); - const requiresMigration = resourceCount > 0 && syncTarget === 'instance'; + // Calculate base requiresMigration: true if there are resources to migrate + const baseRequiresMigration = resourceCount > 0; + + // Calculate final requiresMigration based on sync target and user selection + // For instance sync: always use baseRequiresMigration (checkbox is disabled and always true) + // For folder sync: only migrate if user explicitly opts in via checkbox + const requiresMigration = useMemo(() => { + if (syncTarget === 'instance') { + return baseRequiresMigration; + } + if (syncTarget === 'folder') { + return migrateResources ?? false; + } + return baseRequiresMigration; + }, [syncTarget, baseRequiresMigration, migrateResources]); + const shouldSkipSync = (resourceCount === 0 || syncTarget === 'folder') && fileCount === 0; // Format display strings diff --git a/public/app/features/provisioning/Wizard/types.ts b/public/app/features/provisioning/Wizard/types.ts index 0dc2f001a43..34ee246da04 100644 --- a/public/app/features/provisioning/Wizard/types.ts +++ b/public/app/features/provisioning/Wizard/types.ts @@ -9,6 +9,7 @@ export type RepoType = RepositorySpec['type']; export interface MigrateFormData { history: boolean; identifier: boolean; + migrateResources?: boolean; } export interface WizardFormData { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 79b1f20b8e0..8c7bc95d901 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -12182,6 +12182,9 @@ "tooltip-unhealthy-repository": "Unable to pull an unhealthy repository" }, "synchronize-step": { + "instance-migrate-resources-description": "Instance sync requires all resources to be managed. Existing resources will be migrated automatically.", + "migrate-resources-description": "Import existing dashboards from all folders into the new provisioned folder", + "options": "Options", "repository-error": "Repository error", "repository-error-message": "Unable to check repository status. Please verify the repository configuration and try again.", "repository-unhealthy": "The repository cannot be synchronized. Cancel provisioning and try again once the issue has been resolved. See details below." @@ -12201,11 +12204,16 @@ }, "warning-title-default": "Warning", "wizard": { + "alert-intro": "Please be aware of the following limitations. For more details, see the <2>Git Sync documentation.", "alert-point-1": "Resources can still be created, edited, or deleted during this process, but changes may not be exported.", - "alert-point-2": "Once provisioning is complete, resources will be marked as managed through external storage.", "alert-point-3": "The duration of this process depends on the number of resources involved.", "alert-point-4": "Enterprise instance administrators can display an announcement banner to notify users that migration is in progress. See <2>this guide for step-by-step instructions.", - "alert-title": "Important: No data or configuration will be lost. Dashboards remain accessible during migration, but changes made during this process may not be exported.", + "alert-point-folder-cleanup": "You may need to manually remove or manage original folders after migration.", + "alert-point-folder-structure": "When migrating existing dashboards, the folder structure will be replicated in the repository. Original folders will be emptied of dashboards but may still contain alerts or library panels.", + "alert-point-instance-alerts": "Existing alerts and library panels will be lost and will not be usable after migration.", + "alert-point-permissions": "Fine-grained permissions are not supported. Default permissions apply: Admin, Editor, and Viewer roles are preserved with their standard access levels.", + "alert-point-unsupported": "Alerts and library panels are not supported in provisioned folders.", + "alert-title": "Important: Review Git Sync limitations before proceeding", "button-cancel": "Cancel", "button-cancelling": "Cancelling...", "button-next": "Finish", @@ -12223,6 +12231,7 @@ "step-finish": "Choose additional settings", "step-synchronize": "Synchronize with external storage", "sync-description": "Sync resources with external storage. After this one-time step, all future updates will be automatically saved to the repository and provisioned back into the instance.", + "sync-option-migrate-resources": "Migrate existing resources", "title-bootstrap": "Choose what to synchronize", "title-connect": "Connect to external storage", "title-finish": "Choose additional settings", From 241fd69e028f922bb38de6b46dd1388304b9e62e Mon Sep 17 00:00:00 2001 From: Andre Pereira Date: Thu, 18 Dec 2025 12:38:50 +0000 Subject: [PATCH 219/297] Trace View: Correctly handle span and service name in span filters (#115215) * Correctly handle span name and service name in trace view span filters * Consistency and fix test * i18n extract --- .../SpanFilters/SpanFilters.test.tsx | 262 -------------- .../SpanFilters/SpanFilters.tsx | 319 ------------------ .../SpanFilters/SpanFiltersTags.tsx | 202 ----------- .../useTraceAdHocFiltersController.ts | 15 - .../TraceView/components/constants/span.ts | 2 + .../components/utils/filter-spans.tsx | 20 +- .../explore/TraceView/useSearch.test.ts | 14 +- .../features/explore/TraceView/useSearch.ts | 5 +- .../features/explore/TraceView/utils/tags.ts | 15 + public/locales/en-US/grafana.json | 33 -- 10 files changed, 42 insertions(+), 845 deletions(-) delete mode 100644 public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx delete mode 100644 public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx delete mode 100644 public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx deleted file mode 100644 index eba49425afb..00000000000 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.test.tsx +++ /dev/null @@ -1,262 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { useState } from 'react'; - -import { DEFAULT_SPAN_FILTERS } from 'app/features/explore/state/constants'; - -import { Trace } from '../../types/trace'; - -import { SpanFilters } from './SpanFilters'; - -const trace: Trace = { - traceID: '1ed38015486087ca', - spans: [ - { - traceID: '1ed38015486087ca', - spanID: '1ed38015486087ca', - operationName: 'Span0', - tags: [{ key: 'TagKey0', type: 'string', value: 'TagValue0' }], - kind: 'server', - statusCode: 2, - statusMessage: 'message', - instrumentationLibraryName: 'name', - instrumentationLibraryVersion: 'version', - traceState: 'state', - process: { - serviceName: 'Service0', - tags: [{ key: 'ProcessKey0', type: 'string', value: 'ProcessValue0' }], - }, - logs: [{ fields: [{ key: 'LogKey0', type: 'string', value: 'LogValue0' }] }], - }, - { - traceID: '1ed38015486087ca', - spanID: '2ed38015486087ca', - operationName: 'Span1', - tags: [{ key: 'TagKey1', type: 'string', value: 'TagValue1' }], - process: { - serviceName: 'Service1', - tags: [{ key: 'ProcessKey1', type: 'string', value: 'ProcessValue1' }], - }, - logs: [{ fields: [{ key: 'LogKey1', type: 'string', value: 'LogValue1' }] }], - }, - ], - processes: { - '1ed38015486087ca': { - serviceName: 'Service0', - tags: [], - }, - }, -} as unknown as Trace; - -describe('SpanFilters', () => { - let user: ReturnType; - const SpanFiltersWithProps = ({ showFilters = true, matches }: { showFilters?: boolean; matches?: Set }) => { - const [search, setSearch] = useState(DEFAULT_SPAN_FILTERS); - const props = { - trace: trace, - showSpanFilters: showFilters, - setShowSpanFilters: jest.fn(), - search, - setSearch, - spanFilterMatches: matches, - setFocusedSpanIdForSearch: jest.fn(), - datasourceType: 'tempo', - }; - - return ; - }; - - beforeEach(() => { - jest.useFakeTimers(); - // Need to use delay: null here to work with fakeTimers - // see https://github.com/testing-library/user-event/issues/833 - user = userEvent.setup({ delay: null }); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - it('should render', () => { - expect(() => render()).not.toThrow(); - }); - - it('should render filters', async () => { - render(); - - const serviceOperator = screen.getByLabelText('Select service name operator'); - const serviceValue = screen.getByLabelText('Select service name'); - const spanOperator = screen.getByLabelText('Select span name operator'); - const spanValue = screen.getByLabelText('Select span name'); - const fromOperator = screen.getByLabelText('Select min span operator'); - const fromValue = screen.getByLabelText('Select min span duration'); - const toOperator = screen.getByLabelText('Select max span operator'); - const toValue = screen.getByLabelText('Select max span duration'); - const tagKey = screen.getByLabelText('Select tag key'); - const tagOperator = screen.getByLabelText('Select tag operator'); - const tagSelectValue = screen.getByLabelText('Select tag value'); - - expect(serviceOperator).toBeInTheDocument(); - expect(getElemText(serviceOperator)).toBe('='); - expect(serviceValue).toBeInTheDocument(); - expect(spanOperator).toBeInTheDocument(); - expect(getElemText(spanOperator)).toBe('='); - expect(spanValue).toBeInTheDocument(); - expect(fromOperator).toBeInTheDocument(); - expect(getElemText(fromOperator)).toBe('>'); - expect(fromValue).toBeInTheDocument(); - expect(toOperator).toBeInTheDocument(); - expect(getElemText(toOperator)).toBe('<'); - expect(toValue).toBeInTheDocument(); - expect(tagKey).toBeInTheDocument(); - expect(tagOperator).toBeInTheDocument(); - expect(getElemText(tagOperator)).toBe('='); - expect(tagSelectValue).toBeInTheDocument(); - - await user.click(serviceValue); - jest.advanceTimersByTime(1000); - await waitFor(() => { - expect(screen.getByText('Service0')).toBeInTheDocument(); - expect(screen.getByText('Service1')).toBeInTheDocument(); - }); - await user.click(spanValue); - jest.advanceTimersByTime(1000); - await waitFor(() => { - expect(screen.getByText('Span0')).toBeInTheDocument(); - expect(screen.getByText('Span1')).toBeInTheDocument(); - }); - await user.click(tagOperator); - jest.advanceTimersByTime(1000); - await waitFor(() => { - expect(screen.getByText('!~')).toBeInTheDocument(); - expect(screen.getByText('=~')).toBeInTheDocument(); - expect(screen.getByText('!~')).toBeInTheDocument(); - }); - await user.click(tagKey); - jest.advanceTimersByTime(1000); - await waitFor(() => { - expect(screen.getByText('TagKey0')).toBeInTheDocument(); - expect(screen.getByText('TagKey1')).toBeInTheDocument(); - expect(screen.getByText('kind')).toBeInTheDocument(); - expect(screen.getByText('ProcessKey0')).toBeInTheDocument(); - expect(screen.getByText('ProcessKey1')).toBeInTheDocument(); - expect(screen.getByText('LogKey0')).toBeInTheDocument(); - expect(screen.getByText('LogKey1')).toBeInTheDocument(); - expect(screen.getByPlaceholderText('Find...')).toBeInTheDocument(); - }); - }); - - it('should update filters', async () => { - render(); - const serviceValue = screen.getByLabelText('Select service name'); - const spanValue = screen.getByLabelText('Select span name'); - const tagKey = screen.getByLabelText('Select tag key'); - const tagOperator = screen.getByLabelText('Select tag operator'); - const tagValue = screen.getByLabelText('Select tag value'); - - expect(getElemText(serviceValue)).toBe('All service names'); - await selectAndCheckValue(user, serviceValue, 'Service0'); - expect(getElemText(spanValue)).toBe('All span names'); - await selectAndCheckValue(user, spanValue, 'Span0'); - - await user.click(tagValue); - jest.advanceTimersByTime(1000); - await waitFor(() => expect(screen.getByText('No options found')).toBeInTheDocument()); - - expect(getElemText(tagKey)).toBe('Select tag'); - await selectAndCheckValue(user, tagKey, 'TagKey0'); - expect(getElemText(tagValue)).toBe('Select value'); - await selectAndCheckValue(user, tagValue, 'TagValue0'); - expect(screen.queryByLabelText('Input tag value')).toBeNull(); - await selectAndCheckValue(user, tagOperator, '=~'); - expect(screen.getByLabelText('Input tag value')).toBeInTheDocument(); - }); - - it('should order tag filters', async () => { - render(); - const tagKey = screen.getByLabelText('Select tag key'); - - await user.click(tagKey); - jest.advanceTimersByTime(1000); - await waitFor(() => { - const container = screen.getByText('TagKey0').parentElement?.parentElement?.parentElement; - expect(container?.childNodes[1].textContent).toBe('ProcessKey0'); - expect(container?.childNodes[2].textContent).toBe('ProcessKey1'); - expect(container?.childNodes[3].textContent).toBe('TagKey0'); - expect(container?.childNodes[4].textContent).toBe('TagKey1'); - expect(container?.childNodes[5].textContent).toBe('id'); - expect(container?.childNodes[6].textContent).toBe('kind'); - expect(container?.childNodes[7].textContent).toBe('library.name'); - expect(container?.childNodes[8].textContent).toBe('library.version'); - expect(container?.childNodes[9].textContent).toBe('status'); - expect(container?.childNodes[10].textContent).toBe('status.message'); - expect(container?.childNodes[11].textContent).toBe('trace.state'); - expect(container?.childNodes[12].textContent).toBe('LogKey0'); - expect(container?.childNodes[13].textContent).toBe('LogKey1'); - }); - }); - - it('should only show add/remove tag when necessary', async () => { - render(); - expect(screen.queryAllByLabelText('Add tag').length).toBe(0); // not filled in the default tag, so no need to add another one - expect(screen.queryAllByLabelText('Remove tag').length).toBe(0); // mot filled in the default tag, so no values to remove - expect(screen.getAllByLabelText('Select tag key').length).toBe(1); - - await selectAndCheckValue(user, screen.getByLabelText('Select tag key'), 'TagKey0'); - expect(screen.getAllByLabelText('Add tag').length).toBe(1); - expect(screen.getAllByLabelText('Remove tag').length).toBe(1); - - await user.click(screen.getByLabelText('Add tag')); - jest.advanceTimersByTime(1000); - expect(screen.queryAllByLabelText('Add tag').length).toBe(0); // not filled in the new tag, so no need to add another one - expect(screen.getAllByLabelText('Remove tag').length).toBe(2); // one for each tag - expect(screen.getAllByLabelText('Select tag key').length).toBe(2); - - await user.click(screen.getAllByLabelText('Remove tag')[1]); - jest.advanceTimersByTime(1000); - expect(screen.queryAllByLabelText('Add tag').length).toBe(1); // filled in the default tag, so can add another one - expect(screen.queryAllByLabelText('Remove tag').length).toBe(1); // filled in the default tag, so can remove values - expect(screen.getAllByLabelText('Select tag key').length).toBe(1); - - await user.click(screen.getAllByLabelText('Remove tag')[0]); - jest.advanceTimersByTime(1000); - expect(screen.queryAllByLabelText('Add tag').length).toBe(0); // not filled in the default tag, so no need to add another one - expect(screen.queryAllByLabelText('Remove tag').length).toBe(0); // mot filled in the default tag, so no values to remove - expect(screen.getAllByLabelText('Select tag key').length).toBe(1); - }); - - it('should allow adding/removing tags', async () => { - render(); - expect(screen.getAllByLabelText('Select tag key').length).toBe(1); - const tagKey = screen.getByLabelText('Select tag key'); - await selectAndCheckValue(user, tagKey, 'TagKey0'); - - await user.click(screen.getByLabelText('Add tag')); - jest.advanceTimersByTime(1000); - expect(screen.getAllByLabelText('Select tag key').length).toBe(2); - - await user.click(screen.getAllByLabelText('Remove tag')[0]); - jest.advanceTimersByTime(1000); - expect(screen.getAllByLabelText('Select tag key').length).toBe(1); - }); - - it('renders buttons when span filters is collapsed', async () => { - render(); - expect(screen.queryByRole('button', { name: 'Next result button' })).toBeInTheDocument(); - expect(screen.queryByRole('button', { name: 'Prev result button' })).toBeInTheDocument(); - }); -}); - -const selectAndCheckValue = async (user: ReturnType, elem: HTMLElement, text: string) => { - await user.click(elem); - jest.advanceTimersByTime(1000); - await waitFor(() => expect(screen.getByText(text)).toBeInTheDocument()); - - await user.click(screen.getByText(text)); - jest.advanceTimersByTime(1000); - expect(screen.getByText(text)).toBeInTheDocument(); -}; - -const getElemText = (elem: HTMLElement) => { - return elem.parentElement?.previousSibling?.textContent; -}; diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx deleted file mode 100644 index eda085bc412..00000000000 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFilters.tsx +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { css } from '@emotion/css'; -import React, { useState, useEffect, memo, useCallback, useRef } from 'react'; - -import { GrafanaTheme2, TraceSearchProps, SelectableValue, toOption } from '@grafana/data'; -import { Trans, t } from '@grafana/i18n'; -import { IntervalInput } from '@grafana/o11y-ds-frontend'; -import { Collapse, Icon, InlineField, InlineFieldRow, Select, Stack, Tooltip, useStyles2 } from '@grafana/ui'; - -import { DEFAULT_SPAN_FILTERS } from '../../../../state/constants'; -import { getTraceServiceNames, getTraceSpanNames } from '../../../utils/tags'; -import SearchBarInput from '../../common/SearchBarInput'; -import { Trace } from '../../types/trace'; -import NextPrevResult from '../SearchBar/NextPrevResult'; -import TracePageSearchBar from '../SearchBar/TracePageSearchBar'; - -import { SpanFiltersTags } from './SpanFiltersTags'; - -export type SpanFilterProps = { - trace: Trace; - search: TraceSearchProps; - setSearch: (newSearch: TraceSearchProps) => void; - showSpanFilters: boolean; - setShowSpanFilters: (isOpen: boolean) => void; - setFocusedSpanIdForSearch: React.Dispatch>; - spanFilterMatches: Set | undefined; - datasourceType: string; -}; - -export const SpanFilters = memo((props: SpanFilterProps) => { - const { - trace, - search, - setSearch, - showSpanFilters, - setShowSpanFilters, - setFocusedSpanIdForSearch, - spanFilterMatches, - datasourceType, - } = props; - const styles = { ...useStyles2(getStyles) }; - const [serviceNames, setServiceNames] = useState>>(); - const [spanNames, setSpanNames] = useState>>(); - const [focusedSpanIndexForSearch, setFocusedSpanIndexForSearch] = useState(-1); - const [tagKeys, setTagKeys] = useState>>(); - const [tagValues, setTagValues] = useState<{ [key: string]: Array> }>({}); - const prevTraceIdRef = useRef(); - - const durationRegex = /^\d+(?:\.\d)?\d*(?:ns|us|µs|ms|s|m|h)$/; - - const clear = useCallback(() => { - setServiceNames(undefined); - setSpanNames(undefined); - setTagKeys(undefined); - setTagValues({}); - setSearch(DEFAULT_SPAN_FILTERS); - }, [setSearch]); - - useEffect(() => { - // Only clear filters when trace ID actually changes (not on initial mount) - const currentTraceId = trace?.traceID; - - const traceHasChanged = prevTraceIdRef.current && prevTraceIdRef.current !== currentTraceId; - - if (traceHasChanged) { - clear(); - } - - prevTraceIdRef.current = currentTraceId; - }, [clear, trace]); - - const setShowSpanFilterMatchesOnly = useCallback( - (showMatchesOnly: boolean) => { - setSearch({ ...search, matchesOnly: showMatchesOnly }); - }, - [search, setSearch] - ); - - if (!trace) { - return null; - } - - const setSpanFiltersSearch = (spanSearch: TraceSearchProps) => { - setFocusedSpanIndexForSearch(-1); - setFocusedSpanIdForSearch(''); - setSearch(spanSearch); - }; - - const getServiceNames = () => { - if (!serviceNames) { - setServiceNames(getTraceServiceNames(trace).map(toOption)); - } - }; - - const getSpanNames = () => { - if (!spanNames) { - setSpanNames(getTraceSpanNames(trace).map(toOption)); - } - }; - - const collapseLabel = ( - <> - - - Span Filters - - - - - {!showSpanFilters && ( -
- -
- )} - - ); - - return ( -
- - - - - setSpanFiltersSearch({ ...search, serviceName: v?.value || '' })} - onOpenMenu={getServiceNames} - options={serviceNames || (search.serviceName ? [search.serviceName].map(toOption) : [])} - placeholder={t('explore.span-filters.placeholder-all-service-names', 'All service names')} - value={search.serviceName || null} - defaultValue={search.serviceName || null} - /> - - - { - setSpanFiltersSearch({ ...search, query: v, matchesOnly: v !== '' }); - }} - value={search.query || ''} - /> - - - - - setSpanFiltersSearch({ ...search, spanName: v?.value || '' })} - onOpenMenu={getSpanNames} - options={spanNames || (search.spanName ? [search.spanName].map(toOption) : [])} - placeholder={t('explore.span-filters.placeholder-all-span-names', 'All span names')} - value={search.spanName || null} - /> - - - - - - - setSpanFiltersSearch({ ...search, toOperator: v.value! })} - options={[toOption('<'), toOption('<=')]} - value={search.toOperator} - /> - setSpanFiltersSearch({ ...search, to: val })} - isInvalidError="Invalid duration" - // eslint-disable-next-line @grafana/i18n/no-untranslated-strings - placeholder="e.g. 100ms, 1.2s" - width={18} - value={search.to || ''} - validationRegex={durationRegex} - /> - - - - - - - - - - - -
- ); -}); - -SpanFilters.displayName = 'SpanFilters'; - -const getStyles = (theme: GrafanaTheme2) => ({ - container: css({ - label: 'SpanFilters', - margin: `0.5em 0 -${theme.spacing(1)} 0`, - zIndex: 5, - - '& > div': { - borderLeft: 'none', - borderRight: 'none', - }, - }), - collapseLabel: css({ - svg: { - color: '#aaa', - margin: '-2px 0 0 10px', - }, - }), - flexContainer: css({ - display: 'flex', - justifyContent: 'space-between', - }), - intervalInput: css({ - margin: '0 -4px 0 0', - }), - tagsRow: css({ - margin: '-4px 0 0 0', - }), - nextPrevResult: css({ - flex: 1, - alignItems: 'center', - display: 'flex', - justifyContent: 'flex-end', - marginRight: theme.spacing(1), - }), -}); diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx b/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx deleted file mode 100644 index 3cf2d5f8488..00000000000 --- a/public/app/features/explore/TraceView/components/TracePageHeader/SpanFilters/SpanFiltersTags.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import { css } from '@emotion/css'; -import React from 'react'; -import { useMount } from 'react-use'; - -import { GrafanaTheme2, SelectableValue, toOption, TraceSearchProps, TraceSearchTag } from '@grafana/data'; -import { t } from '@grafana/i18n'; -import { AccessoryButton } from '@grafana/plugin-ui'; -import { Input, Select, Stack, useStyles2 } from '@grafana/ui'; - -import { randomId } from '../../../../state/constants'; -import { getTraceTagKeys, getTraceTagValues } from '../../../utils/tags'; -import { Trace } from '../../types/trace'; - -interface Props { - search: TraceSearchProps; - setSearch: (search: TraceSearchProps) => void; - trace: Trace; - tagKeys?: Array>; - setTagKeys: React.Dispatch> | undefined>>; - tagValues: Record>>; - setTagValues: React.Dispatch> }>>; -} - -export const SpanFiltersTags = ({ search, trace, setSearch, tagKeys, setTagKeys, tagValues, setTagValues }: Props) => { - const styles = { ...useStyles2(getStyles) }; - - const getTagKeys = () => { - if (!tagKeys) { - setTagKeys(getTraceTagKeys(trace).map(toOption)); - } - }; - - const getTagValues = (key: string) => { - return getTraceTagValues(trace, key).map(toOption); - }; - - useMount(() => { - if (search.tags) { - search.tags.forEach((tag) => { - if (tag.key) { - setTagValues({ - ...tagValues, - [tag.id]: getTagValues(tag.key), - }); - } - }); - } - }); - - const onTagChange = (tag: TraceSearchTag, v: SelectableValue) => { - setSearch({ - ...search, - tags: search.tags?.map((x) => { - return x.id === tag.id ? { ...x, key: v?.value || '', value: undefined } : x; - }), - }); - - const loadTagValues = async () => { - if (v?.value) { - setTagValues({ - ...tagValues, - [tag.id]: getTagValues(v.value), - }); - } else { - // removed value - const updatedValues = { ...tagValues }; - if (updatedValues[tag.id]) { - delete updatedValues[tag.id]; - } - setTagValues(updatedValues); - } - }; - loadTagValues(); - }; - - const addTag = () => { - const tag = { - id: randomId(), - operator: '=', - }; - setSearch({ ...search, tags: [...search.tags, tag] }); - }; - - const removeTag = (id: string) => { - let tags = search.tags.filter((tag) => { - return tag.id !== id; - }); - if (tags.length === 0) { - tags = [ - { - id: randomId(), - operator: '=', - }, - ]; - } - setSearch({ ...search, tags: tags }); - }; - - return ( -
- {search.tags?.map((tag, i) => ( -
- -
- { - setSearch({ - ...search, - tags: search.tags?.map((x) => { - return x.id === tag.id ? { ...x, operator: v.value! } : x; - }), - }); - }} - options={[toOption('='), toOption('!='), toOption('=~'), toOption('!~')]} - value={tag.operator} - /> -
- - - {(tag.operator === '=' || tag.operator === '!=') && ( - { - setSearch({ - ...search, - tags: search.tags?.map((x) => { - return x.id === tag.id ? { ...x, value: v?.currentTarget?.value || '' } : x; - }), - }); - }} - placeholder={t('explore.span-filters-tags.placeholder-tag-value', 'Tag value')} - width={18} - value={tag.value || ''} - /> - )} - - {(tag.key || tag.value || search.tags.length > 1) && ( - removeTag(tag.id)} - tooltip={t('explore.span-filters-tags.tooltip-remove-tag', 'Remove tag')} - /> - )} - {(tag.key || tag.value) && i === search.tags.length - 1 && ( - - - - )} -
-
- ))} -
- ); -}; - -const getStyles = (theme: GrafanaTheme2) => ({ - addTag: css({ - marginLeft: theme.spacing(1), - }), - tagValues: css({ - maxWidth: '200px', - }), -}); diff --git a/public/app/features/explore/TraceView/components/TracePageHeader/useTraceAdHocFiltersController.ts b/public/app/features/explore/TraceView/components/TracePageHeader/useTraceAdHocFiltersController.ts index 8615c446294..c5286297087 100644 --- a/public/app/features/explore/TraceView/components/TracePageHeader/useTraceAdHocFiltersController.ts +++ b/public/app/features/explore/TraceView/components/TracePageHeader/useTraceAdHocFiltersController.ts @@ -1,18 +1,3 @@ -// Copyright (c) 2025 Grafana Labs -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - import { useMemo, useState } from 'react'; import { TraceSearchProps } from '@grafana/data'; diff --git a/public/app/features/explore/TraceView/components/constants/span.ts b/public/app/features/explore/TraceView/components/constants/span.ts index 8d2fb1e5052..2e4d04be71b 100644 --- a/public/app/features/explore/TraceView/components/constants/span.ts +++ b/public/app/features/explore/TraceView/components/constants/span.ts @@ -5,3 +5,5 @@ export const LIBRARY_NAME = 'library.name'; export const LIBRARY_VERSION = 'library.version'; export const TRACE_STATE = 'trace.state'; export const ID = 'id'; +export const SPAN_NAME = 'span.name'; +export const SERVICE_NAME = 'service.name'; diff --git a/public/app/features/explore/TraceView/components/utils/filter-spans.tsx b/public/app/features/explore/TraceView/components/utils/filter-spans.tsx index d03016dab78..69c42ee2e8f 100644 --- a/public/app/features/explore/TraceView/components/utils/filter-spans.tsx +++ b/public/app/features/explore/TraceView/components/utils/filter-spans.tsx @@ -16,7 +16,17 @@ import { SpanStatusCode } from '@opentelemetry/api'; import { SelectableValue, TraceKeyValuePair, TraceSearchProps, TraceSearchTag } from '@grafana/data'; -import { KIND, LIBRARY_NAME, LIBRARY_VERSION, STATUS, STATUS_MESSAGE, TRACE_STATE, ID } from '../constants/span'; +import { + KIND, + LIBRARY_NAME, + LIBRARY_VERSION, + STATUS, + STATUS_MESSAGE, + TRACE_STATE, + ID, + SPAN_NAME, + SERVICE_NAME, +} from '../constants/span'; import TNil from '../types/TNil'; import { TraceSpan, CriticalPathSection } from '../types/trace'; @@ -46,13 +56,13 @@ const getAdhocFilterMatches = (spans: TraceSpan[], adhocFilters: Array { // Check that adhoc filter was created expect(result.current.search.adhocFilters).toHaveLength(1); expect(result.current.search.adhocFilters?.[0]).toMatchObject({ - key: 'serviceName', + key: 'service.name', operator: '=', value: 'my-service', }); @@ -120,7 +120,7 @@ describe('useSearch', () => { // Check that adhoc filter was created expect(result.current.search.adhocFilters).toHaveLength(1); expect(result.current.search.adhocFilters?.[0]).toMatchObject({ - key: 'spanName', + key: 'span.name', operator: '!=', value: 'my-operation', }); @@ -195,13 +195,13 @@ describe('useSearch', () => { // Verify each filter const filters = result.current.search.adhocFilters || []; - expect(filters.find((f) => f.key === 'serviceName')).toMatchObject({ - key: 'serviceName', + expect(filters.find((f) => f.key === 'service.name')).toMatchObject({ + key: 'service.name', operator: '=', value: 'my-service', }); - expect(filters.find((f) => f.key === 'spanName')).toMatchObject({ - key: 'spanName', + expect(filters.find((f) => f.key === 'span.name')).toMatchObject({ + key: 'span.name', operator: '!=', value: 'my-operation', }); @@ -306,7 +306,7 @@ describe('useSearch', () => { expect(result.current.search.adhocFilters).toHaveLength(5); const filters = result.current.search.adhocFilters || []; - expect(filters.find((f) => f.key === 'serviceName')?.operator).toBe('!='); + expect(filters.find((f) => f.key === 'service.name')?.operator).toBe('!='); expect(filters.find((f) => f.key === 'tag1')?.operator).toBe('='); expect(filters.find((f) => f.key === 'tag2')?.operator).toBe('!='); expect(filters.find((f) => f.key === 'tag3')?.operator).toBe('=~'); diff --git a/public/app/features/explore/TraceView/useSearch.ts b/public/app/features/explore/TraceView/useSearch.ts index 9deb191a8d3..086866b87fb 100644 --- a/public/app/features/explore/TraceView/useSearch.ts +++ b/public/app/features/explore/TraceView/useSearch.ts @@ -7,6 +7,7 @@ import { useDispatch, useSelector } from 'app/types/store'; import { DEFAULT_SPAN_FILTERS, randomId } from '../state/constants'; import { changePanelState } from '../state/explorePane'; +import { SPAN_NAME, SERVICE_NAME } from './components/constants/span'; import { TraceSpan, CriticalPathSection } from './components/types/trace'; import { filterSpans } from './components/utils/filter-spans'; @@ -25,7 +26,7 @@ export function migrateToAdhocFilters(search: TraceSearchProps): TraceSearchProp // Migrate serviceName if (search.serviceName && search.serviceName.trim() !== '') { adhocFilters.push({ - key: 'serviceName', + key: SERVICE_NAME, operator: search.serviceNameOperator || '=', value: search.serviceName, }); @@ -34,7 +35,7 @@ export function migrateToAdhocFilters(search: TraceSearchProps): TraceSearchProp // Migrate spanName if (search.spanName && search.spanName.trim() !== '') { adhocFilters.push({ - key: 'spanName', + key: SPAN_NAME, operator: search.spanNameOperator || '=', value: search.spanName, }); diff --git a/public/app/features/explore/TraceView/utils/tags.ts b/public/app/features/explore/TraceView/utils/tags.ts index 293326e359e..38ae3c92148 100644 --- a/public/app/features/explore/TraceView/utils/tags.ts +++ b/public/app/features/explore/TraceView/utils/tags.ts @@ -9,6 +9,8 @@ import { STATUS, STATUS_MESSAGE, TRACE_STATE, + SPAN_NAME, + SERVICE_NAME, } from '../components/constants/span'; import { Trace } from '../components/types/trace'; @@ -37,6 +39,11 @@ export const getTraceTagKeys = (trace: Trace) => { span.process.tags.forEach((tag) => { keys.push(tag.key); }); + + if (span.process.serviceName) { + keys.push(SERVICE_NAME); + } + if (span.logs !== null) { span.logs.forEach((log) => { log.fields.forEach((field) => { @@ -63,6 +70,9 @@ export const getTraceTagKeys = (trace: Trace) => { if (span.traceState) { keys.push(TRACE_STATE); } + if (span.operationName) { + keys.push(SPAN_NAME); + } keys.push(ID); }); keys = uniq(keys).sort(); @@ -93,6 +103,11 @@ export const getTraceTagValues = (trace: Trace, key: string) => { } switch (key) { + case SPAN_NAME: + if (span.operationName) { + values.push(span.operationName); + } + break; case KIND: if (span.kind) { values.push(span.kind); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 8c7bc95d901..25ad73abe1a 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -7641,39 +7641,6 @@ }, "share-span": "Share" }, - "span-filters": { - "aria-label-select-max-span-operator": "Select max span operator", - "aria-label-select-min-span-operator": "Select min span operator", - "aria-label-select-service-name": "Select service name", - "aria-label-select-service-name-operator": "Select service name operator", - "aria-label-select-span-name": "Select span name", - "aria-label-select-span-name-operator": "Select span name operator", - "ariaLabel-select-max-span-duration": "Select max span duration", - "ariaLabel-select-min-span-duration": "Select min span duration", - "label-collapse": "Span Filters", - "label-duration": "Duration", - "label-service-name": "Service name", - "label-span-name": "Span name", - "label-tags": "Tags", - "placeholder-all-service-names": "All service names", - "placeholder-all-span-names": "All span names", - "tooltip-collapse": "Filter your spans below. You can continue to apply filters until you have narrowed down your resulting spans to the select few you are most interested in.", - "tooltip-duration": "Filter by duration. Accepted units are {{units}}", - "tooltip-tags": "Filter by tags, process tags or log fields in your spans." - }, - "span-filters-tags": { - "aria-label-add-tag": "Add tag", - "aria-label-input-tag-value": "Input tag value", - "aria-label-remove-tag": "Remove tag", - "aria-label-select-tag-key": "Select tag key", - "aria-label-select-tag-operator": "Select tag operator", - "aria-label-select-tag-value": "Select tag value", - "placeholder-select-tag": "Select tag", - "placeholder-select-value": "Select value", - "placeholder-tag-value": "Tag value", - "tooltip-add-tag": "Add tag", - "tooltip-remove-tag": "Remove tag" - }, "span-flame-graph": { "flame-graph": "Flame graph" }, From 90af2c3c3b6c28e66506e0e67f7acd77959efbe9 Mon Sep 17 00:00:00 2001 From: Rafael Bortolon Paulovic Date: Thu, 18 Dec 2025 14:11:47 +0100 Subject: [PATCH 220/297] fix(dashboard): panic on nil logger on dashboard accessor (#115545) fix(dashboard): fix panic on log --- pkg/registry/apis/dashboard/legacy/sql_dashboards.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index 0a496e4667b..0b7ffa04ea4 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -113,6 +113,7 @@ func ProvideMigratorDashboardAccessor( dashboardPermissionSvc: nil, // not needed for migration libraryPanelSvc: nil, // not needed for migration accessControl: accessControl, + log: log.New("legacy.dashboard.migrator.accessor"), } } @@ -136,6 +137,7 @@ func NewDashboardSQLAccess(sql legacysql.LegacyDatabaseProvider, dashboardPermissionSvc: dashboardPermissionSvc, libraryPanelSvc: libraryPanelSvc, accessControl: accessControl, + log: log.New("legacy.dashboard.accessor"), } } From 14ef6ca4eb265bf19fddf669ab5d7c982116b00d Mon Sep 17 00:00:00 2001 From: Mariell Hoversholm Date: Thu, 18 Dec 2025 14:23:07 +0100 Subject: [PATCH 221/297] docs: remove SECURITY.md (#115549) --- .github/CODEOWNERS | 1 - SECURITY.md | 29 ----------------------------- 2 files changed, 30 deletions(-) delete mode 100644 SECURITY.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7ef6be6644c..4cac8f6dd81 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -24,7 +24,6 @@ /NOTICE.md @torkelo /README.md @grafana/docs-grafana /ROADMAP.md @torkelo -/SECURITY.md @grafana/security-team /SUPPORT.md @torkelo /WORKFLOW.md @torkelo /contribute/ @grafana/grafana-community-support diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 42c58f9cd67..00000000000 --- a/SECURITY.md +++ /dev/null @@ -1,29 +0,0 @@ -# Reporting security issues - -If you think you have found a security vulnerability, we have two routes for reporting security issues. - -Important: Whichever route you choose, we ask you to not disclose the vulnerability before it has been fixed and announced, unless you received a response from the Grafana Labs security team that you can do so. - -[Full guidance on reporting a security issue can be found here](https://grafana.com/legal/report-a-security-issue/). - -This product is in scope for our Bug Bounty Program. To submit a vulnerability report, please visit [Grafana Labs Bug Bounty page](https://app.intigriti.com/programs/grafanalabs/grafanaossbbp/detail) and follow the instructions provided. Our security team will review your submission and get back to you as soon as possible. - ---- - -For products and services outside the scope of our bug bounty program, or if you do not wish to receive a bounty, you can report issues directly to us via email at security@grafana.com. This address can be used for all of Grafana Labs’ open source and commercial products (including but not limited to Grafana, Grafana Cloud, Grafana Enterprise, and grafana.com). - -Please encrypt your message to us; please use our PGP key. The key fingerprint is: - -225E 6A9B BB15 A37E 95EB 6312 C66A 51CC B44C 27E0 - -The key is available from [keyserver.ubuntu.com](https://keyserver.ubuntu.com/pks/lookup?search=0x225E6A9BBB15A37E95EB6312C66A51CCB44C27E0&fingerprint=on&op=index). - -Grafana Labs will send you a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. - -**Important:** We ask you to not disclose the vulnerability before it have been fixed and announced, unless you received a response from the Grafana Labs security team that you can do so. - -## Security announcements - -We will post a summary, remediation, and mitigation details for any patch containing security fixes on the Grafana blog. The security announcement blog posts will be tagged with the [security tag](https://grafana.com/tags/security/). - -You can also track security announcements via the [RSS feed](https://grafana.com/tags/security/index.xml). From 39fa6559ee01a5250b9fbc15c63ba63215af9bf3 Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:46:24 +0100 Subject: [PATCH 222/297] CI: Remove the default alpine & ubuntu versions so that the ones in Dockerfile (#115544) * Remove the default alpine & ubuntu versions so that the ones in Dockerfile are used * set default to just 'alpine' or 'ubuntu' * use defaults instead --- .github/actions/build-package/action.yml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/actions/build-package/action.yml b/.github/actions/build-package/action.yml index dd5aaa13650..978c6645240 100644 --- a/.github/actions/build-package/action.yml +++ b/.github/actions/build-package/action.yml @@ -82,14 +82,6 @@ inputs: description: Docker registry of produced images default: docker.io required: false - ubuntu-base: - type: string - default: 'ubuntu:22.04' - required: false - alpine-base: - type: string - default: 'alpine:3.22' - required: false outputs: dist-dir: description: Directory where artifacts are placed @@ -134,13 +126,11 @@ runs: UBUNTU_TAG_FORMAT: ${{ inputs.docker-tag-format-ubuntu }} CHECKSUM: ${{ inputs.checksum }} VERIFY: ${{ inputs.verify }} - ALPINE_BASE: ${{ inputs.alpine-base }} - UBUNTU_BASE: ${{ inputs.ubuntu-base }} with: verb: run dagger-flags: --verbose=0 version: 0.18.8 - args: go run -C ${GRAFANA_PATH} ./pkg/build/cmd artifacts --artifacts ${ARTIFACTS} --grafana-dir=${GRAFANA_PATH} --alpine-base=${ALPINE_BASE} --ubuntu-base=${UBUNTU_BASE} --enterprise-dir=${ENTERPRISE_PATH} --version=${VERSION} --patches-repo=${PATCHES_REPO} --patches-ref=${PATCHES_REF} --patches-path=${PATCHES_PATH} --build-id=${BUILD_ID} --tag-format="${TAG_FORMAT}" --ubuntu-tag-format="${UBUNTU_TAG_FORMAT}" --org=${DOCKER_ORG} --registry=${DOCKER_REGISTRY} --checksum=${CHECKSUM} --verify=${VERIFY} > $OUTFILE + args: go run -C ${GRAFANA_PATH} ./pkg/build/cmd artifacts --artifacts ${ARTIFACTS} --grafana-dir=${GRAFANA_PATH} --enterprise-dir=${ENTERPRISE_PATH} --version=${VERSION} --patches-repo=${PATCHES_REPO} --patches-ref=${PATCHES_REF} --patches-path=${PATCHES_PATH} --build-id=${BUILD_ID} --tag-format="${TAG_FORMAT}" --ubuntu-tag-format="${UBUNTU_TAG_FORMAT}" --org=${DOCKER_ORG} --registry=${DOCKER_REGISTRY} --checksum=${CHECKSUM} --verify=${VERIFY} > $OUTFILE - id: output shell: bash env: From 5c7cdabaa39c26b4024d114f565d05077f72fa39 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Thu, 18 Dec 2025 14:58:39 +0100 Subject: [PATCH 223/297] Alerting: Improve performance of rule list view with limit_alerts=0 (#115548) Alerting: Improve performance of rule list view --- .../ngalert/api/prometheus/api_prometheus.go | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/pkg/services/ngalert/api/prometheus/api_prometheus.go b/pkg/services/ngalert/api/prometheus/api_prometheus.go index 934805d74f4..b4e14a66cfe 100644 --- a/pkg/services/ngalert/api/prometheus/api_prometheus.go +++ b/pkg/services/ngalert/api/prometheus/api_prometheus.go @@ -357,7 +357,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon type RuleStatusMutator func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule) // mutator function used to attach alert states to the rule and returns the totals and filtered totals -type RuleAlertStateMutator func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule, stateFilterSet map[eval.State]struct{}, matchers labels.Matchers, labelOptions []ngmodels.LabelOption) (total map[string]int64, filteredTotal map[string]int64) +type RuleAlertStateMutator func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule, stateFilterSet map[eval.State]struct{}, matchers labels.Matchers, labelOptions []ngmodels.LabelOption, limitAlerts int64) (total map[string]int64, filteredTotal map[string]int64) func RuleStatusMutatorGenerator(statusReader StatusReader) RuleStatusMutator { return func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule) { @@ -377,32 +377,18 @@ func RuleStatusMutatorGenerator(statusReader StatusReader) RuleStatusMutator { } func RuleAlertStateMutatorGenerator(manager state.AlertInstanceManager) RuleAlertStateMutator { - return func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule, stateFilterSet map[eval.State]struct{}, matchers labels.Matchers, labelOptions []ngmodels.LabelOption) (map[string]int64, map[string]int64) { + return func(source *ngmodels.AlertRule, toMutate *apimodels.AlertingRule, stateFilterSet map[eval.State]struct{}, matchers labels.Matchers, labelOptions []ngmodels.LabelOption, limitAlerts int64) (map[string]int64, map[string]int64) { states := manager.GetStatesForRuleUID(source.OrgID, source.UID) totals := make(map[string]int64) totalsFiltered := make(map[string]int64) for _, alertState := range states { activeAt := alertState.StartsAt - valString := "" - if alertState.State == eval.Alerting || alertState.State == eval.Pending || alertState.State == eval.Recovering { - valString = FormatValues(alertState) - } stateKey := strings.ToLower(alertState.State.String()) totals[stateKey] += 1 // Do not add error twice when execution error state is Error if alertState.Error != nil && source.ExecErrState != ngmodels.ErrorErrState { totals["error"] += 1 } - alert := apimodels.Alert{ - Labels: apimodels.LabelsFromMap(alertState.GetLabels(labelOptions...)), - Annotations: apimodels.LabelsFromMap(alertState.Annotations), - - // TODO: or should we make this two fields? Using one field lets the - // frontend use the same logic for parsing text on annotations and this. - State: state.FormatStateAndReason(alertState.State, alertState.StateReason), - ActiveAt: &activeAt, - Value: valString, - } // Set the state of the rule based on the state of its alerts. // Only update the rule state with 'pending' or 'recovering' if the current state is 'inactive'. @@ -442,7 +428,23 @@ func RuleAlertStateMutatorGenerator(manager state.AlertInstanceManager) RuleAler totalsFiltered["error"] += 1 } - toMutate.Alerts = append(toMutate.Alerts, alert) + if limitAlerts != 0 { + valString := "" + if alertState.State == eval.Alerting || alertState.State == eval.Pending || alertState.State == eval.Recovering { + valString = FormatValues(alertState) + } + + toMutate.Alerts = append(toMutate.Alerts, apimodels.Alert{ + Labels: apimodels.LabelsFromMap(alertState.GetLabels(labelOptions...)), + Annotations: apimodels.LabelsFromMap(alertState.Annotations), + + // TODO: or should we make this two fields? Using one field lets the + // frontend use the same logic for parsing text on annotations and this. + State: state.FormatStateAndReason(alertState.State, alertState.StateReason), + ActiveAt: &activeAt, + Value: valString, + }) + } } return totals, totalsFiltered } @@ -1227,7 +1229,7 @@ func toRuleGroup(log log.Logger, groupKey ngmodels.AlertRuleGroupKey, folderFull } // mutate rule for alert states - totals, totalsFiltered := ruleAlertStateMutator(rule, &alertingRule, stateFilterSet, matchers, labelOptions) + totals, totalsFiltered := ruleAlertStateMutator(rule, &alertingRule, stateFilterSet, matchers, labelOptions, limitAlerts) if alertingRule.State != "" { rulesTotals[alertingRule.State] += 1 From 4fbcebac2c78c48b4338881b42b5c39db7c8b9c5 Mon Sep 17 00:00:00 2001 From: "Marc M." <146180665+grafakus@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:01:04 +0100 Subject: [PATCH 224/297] Deps: Upgrade Scenes to v6.51.0 (#115547) Scenes: Upgrade to v6.51.0 --- package.json | 4 ++-- yarn.lock | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index f2300869740..37dc6ff745c 100644 --- a/package.json +++ b/package.json @@ -295,8 +295,8 @@ "@grafana/plugin-ui": "^0.11.1", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "6.50.0", - "@grafana/scenes-react": "6.50.0", + "@grafana/scenes": "^6.51.0", + "@grafana/scenes-react": "^6.51.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/yarn.lock b/yarn.lock index e20676b04f0..a60a9c08912 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3604,11 +3604,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:6.50.0": - version: 6.50.0 - resolution: "@grafana/scenes-react@npm:6.50.0" +"@grafana/scenes-react@npm:^6.51.0": + version: 6.51.0 + resolution: "@grafana/scenes-react@npm:6.51.0" dependencies: - "@grafana/scenes": "npm:6.50.0" + "@grafana/scenes": "npm:6.51.0" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3620,7 +3620,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/9ac9f8a32699f447c7b67dd2aef4e3ca5bc9fc98e94e0dc139e7824274ffa005b7fb3fc42ca5e55bdf89b91e3af0d3807b03e1a261db91c65717ee1763e5e807 + checksum: 10/14acdfe5220e67e7450780320b779e2e4a255995d55f0c82eb0d25933e72598e54826df0a8beee05591efe01a91ddab840483fea3bb828bd5925c3f0b44b8d17 languageName: node linkType: hard @@ -3650,9 +3650,9 @@ __metadata: languageName: node linkType: hard -"@grafana/scenes@npm:6.50.0": - version: 6.50.0 - resolution: "@grafana/scenes@npm:6.50.0" +"@grafana/scenes@npm:6.51.0, @grafana/scenes@npm:^6.51.0": + version: 6.51.0 + resolution: "@grafana/scenes@npm:6.51.0" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3672,7 +3672,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/7bc6280ff065bbba37f010e2a1f0a7dc998fe43721ddc0121e27a754c41e824b82a44222100282a69143a52061cf0dce39e6bc8b95292ca444a59c114d4b5a41 + checksum: 10/4e4f43babe786ff729d58b7636182df57c58ce40c13b56036f725c070e0cf597cbe52aaa0f811184b8d42d8d1f9a32679695471d410f883051b09da44f8bf36a languageName: node linkType: hard @@ -19508,8 +19508,8 @@ __metadata: "@grafana/plugin-ui": "npm:^0.11.1" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:6.50.0" - "@grafana/scenes-react": "npm:6.50.0" + "@grafana/scenes": "npm:^6.51.0" + "@grafana/scenes-react": "npm:^6.51.0" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/test-utils": "workspace:*" From f1b19dd9faccc5e868c18c5ae7def8cea7438dd8 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Thu, 18 Dec 2025 15:38:15 +0100 Subject: [PATCH 225/297] ElasticSearch: Update annotation time-range properties (#115500) Update time-range properties --- .../plugins/datasource/elasticsearch/datasource.test.ts | 6 +++--- public/app/plugins/datasource/elasticsearch/datasource.ts | 8 ++++---- public/app/plugins/datasource/elasticsearch/types.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/public/app/plugins/datasource/elasticsearch/datasource.test.ts b/public/app/plugins/datasource/elasticsearch/datasource.test.ts index 33c3486bd31..6e3c7b2c2ce 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.test.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.test.ts @@ -1000,7 +1000,7 @@ describe('ElasticDatasource', () => { }); expect(postResourceRequestMock).toHaveBeenCalledWith( '_msearch', - '{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@test_time":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}},{"range":{"@time_end_field":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}},{"query_string":{"query":"abc"}}]}},"size":10000}\n' + '{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@test_time":{"gte":1683291160012,"lte":1683291460012,"format":"epoch_millis"}}},{"range":{"@time_end_field":{"gte":1683291160012,"lte":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}},{"query_string":{"query":"abc"}}]}},"size":10000}\n' ); }); @@ -1030,7 +1030,7 @@ describe('ElasticDatasource', () => { }); expect(postResourceRequestMock).toHaveBeenCalledWith( '_msearch', - '{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}}]}},"size":10000}\n' + '{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1683291160012,"lte":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}}]}},"size":10000}\n' ); }); @@ -1087,7 +1087,7 @@ describe('ElasticDatasource', () => { }); expect(postResourceRequestMock).toHaveBeenCalledWith( '_msearch', - '{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@test_time":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}},{"range":{"@time_end_field":{"from":1683291160012,"to":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}},{"query_string":{"query":"abc AND abc_key:\\"abc_value\\""}}]}},"size":10000}\n' + '{"search_type":"query_then_fetch","ignore_unavailable":true,"index":"[test-]YYYY.MM.DD"}\n{"query":{"bool":{"filter":[{"bool":{"should":[{"range":{"@test_time":{"gte":1683291160012,"lte":1683291460012,"format":"epoch_millis"}}},{"range":{"@time_end_field":{"gte":1683291160012,"lte":1683291460012,"format":"epoch_millis"}}}],"minimum_should_match":1}},{"query_string":{"query":"abc AND abc_key:\\"abc_value\\""}}]}},"size":10000}\n' ); }); }); diff --git a/public/app/plugins/datasource/elasticsearch/datasource.ts b/public/app/plugins/datasource/elasticsearch/datasource.ts index d9294e5e62e..95e329e03ea 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.ts @@ -294,8 +294,8 @@ export class ElasticDatasource const dateRanges = []; const rangeStart: RangeMap = {}; rangeStart[timeField] = { - from: options.range.from.valueOf(), - to: options.range.to.valueOf(), + gte: options.range.from.valueOf(), + lte: options.range.to.valueOf(), format: 'epoch_millis', }; dateRanges.push({ range: rangeStart }); @@ -303,8 +303,8 @@ export class ElasticDatasource if (timeEndField) { const rangeEnd: RangeMap = {}; rangeEnd[timeEndField] = { - from: options.range.from.valueOf(), - to: options.range.to.valueOf(), + gte: options.range.from.valueOf(), + lte: options.range.to.valueOf(), format: 'epoch_millis', }; dateRanges.push({ range: rangeEnd }); diff --git a/public/app/plugins/datasource/elasticsearch/types.ts b/public/app/plugins/datasource/elasticsearch/types.ts index c3a7c5f9da2..4645a2a824f 100644 --- a/public/app/plugins/datasource/elasticsearch/types.ts +++ b/public/app/plugins/datasource/elasticsearch/types.ts @@ -137,7 +137,7 @@ export interface ElasticsearchAnnotationQuery { index?: string; } -export type RangeMap = Record; +export type RangeMap = Record; export type ElasticsearchResponse = ElasticsearchResponseWithHits | ElasticsearchResponseWithAggregations; From 4bcd31b17acb8f6d8af55452078a914863077282 Mon Sep 17 00:00:00 2001 From: Kristina Demeshchik Date: Thu, 18 Dec 2025 09:59:26 -0500 Subject: [PATCH 226/297] Dashboard: change export dropdown placement in sidebar (#115515) Update export menu placement --- .../dashboard-scene/edit-pane/DashboardExportButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardExportButton.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardExportButton.tsx index 35d8afbdb4d..e048a016868 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardExportButton.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardExportButton.tsx @@ -18,7 +18,7 @@ const newExportButtonSelector = selectors.pages.Dashboard.DashNav.NewExportButto export function ShareExportDashboardButton({ dashboard }: Props) { return ( - } placement="left-end"> + } placement="left-start"> Date: Thu, 18 Dec 2025 09:59:58 -0500 Subject: [PATCH 227/297] Dashboards: Fix text panel content loss during v1 to v2 migration (#115496) * move content and mode properties to options level * move to angular section * Update comments * handle missing angular text panel * re-generate test files * angualr panels tests * fixing test * Update output files * Update output for dev dashboard * Spread options at the top panel level for migration * linting issue --------- Co-authored-by: Ivan Ortega --- .../migrations/v0alpha1.migrations.v42.json | 12 +- ...-v16.grid_layout_upgrade.v42.v2alpha1.json | 9 +- ...g-v16.grid_layout_upgrade.v42.v2beta1.json | 9 +- ...g-v2.panels-and-services.v42.v2alpha1.json | 15 +- ...ig-v2.panels-and-services.v42.v2beta1.json | 15 +- ...a1-mig-v24.table-angular.v42.v2alpha1.json | 24 +- ...ta1-mig-v24.table-angular.v42.v2beta1.json | 24 +- ...a1-mig-v26.text2_to_text.v42.v2alpha1.json | 10 +- ...ta1-mig-v26.text2_to_text.v42.v2beta1.json | 10 +- ...lpha1.testdata-datalinks.v42.v2alpha1.json | 10 +- ...alpha1.testdata-datalinks.v42.v2beta1.json | 10 +- ...sted-variables-drilldown.v42.v2alpha1.json | 10 +- ...ested-variables-drilldown.v42.v2beta1.json | 10 +- ...estdata-nested-variables.v42.v2alpha1.json | 10 +- ...testdata-nested-variables.v42.v2beta1.json | 10 +- .../v0alpha1.migrations.v42.v1beta1.json | 11 +- .../v0alpha1.migrations.v42.v2alpha1.json | 14 +- .../v0alpha1.migrations.v42.v2beta1.json | 14 +- .../v0alpha1.gauge_tests.v42.v2alpha1.json | 72 + .../v0alpha1.gauge_tests.v42.v2beta1.json | 72 + .../v0alpha1.graph_tests.v42.v2alpha1.json | 72 +- .../v0alpha1.graph_tests.v42.v2beta1.json | 72 +- .../v0alpha1.heatmap-legacy.v42.v2alpha1.json | 238 +- .../v0alpha1.heatmap-legacy.v42.v2beta1.json | 238 +- .../v0alpha1.polystat_test.v42.v2alpha1.json | 3277 ++++++++++++++++- .../v0alpha1.polystat_test.v42.v2beta1.json | 3277 ++++++++++++++++- ...beta1.v1beta1.all-panels.v42.v2alpha1.json | 37 +- ...1beta1.v1beta1.all-panels.v42.v2beta1.json | 37 +- ...ha1.v1beta1.v1beta1.home.v42.v2alpha1.json | 50 + ...pha1.v1beta1.v1beta1.home.v42.v2beta1.json | 50 + .../conversion/v1beta1_to_v2alpha1.go | 6 +- .../migrations/migrations.v42.json | 11 +- .../dev-dashboards/migrations/migrations.json | 12 +- eslint-suppressions.json | 5 - .../serialization/angularMigration.test.ts | 151 +- .../serialization/angularMigration.ts | 38 +- .../dashboard/api/ResponseTransformers.ts | 16 +- 37 files changed, 7835 insertions(+), 123 deletions(-) diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.json index 89ce80876d2..e4e6f8831da 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.json @@ -300,15 +300,9 @@ "y": 0 }, "id": 6, - "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", - "mode": "markdown" - }, + "options": {}, + "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", + "mode": "markdown", "pluginVersion": "11.0.0-pre", "targets": [ { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json index 7229c6df268..c4198fa24b9 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2alpha1.json @@ -115,7 +115,14 @@ "kind": "logs", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "logs", + "originalOptions": { + "height": 100 + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json index efdbb91f745..578e9d707ad 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v16.grid_layout_upgrade.v42.v2beta1.json @@ -120,7 +120,14 @@ "group": "logs", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "logs", + "originalOptions": { + "height": 100 + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json index 57b7b24ed56..d30c3ce7923 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2alpha1.json @@ -182,7 +182,20 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table", + "originalOptions": { + "grid": { + "max": 100, + "min": 0 + }, + "legend": true, + "y2_format": "bytes", + "y_format": "short" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json index 0143563e75b..6a02d64f5b2 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v2.panels-and-services.v42.v2beta1.json @@ -189,7 +189,20 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table", + "originalOptions": { + "grid": { + "max": 100, + "min": 0 + }, + "legend": true, + "y2_format": "bytes", + "y_format": "short" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json index 4734298869d..6fa76798215 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2alpha1.json @@ -435,7 +435,29 @@ "kind": "table", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table", + "originalOptions": { + "styles": [ + { + "colors": [ + "red", + "yellow", + "green" + ], + "pattern": "/.*/", + "thresholds": [ + "10", + "20" + ], + "unit": "short" + } + ], + "table": "table2" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json index 57979e9f551..9ae87fe0f32 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v24.table-angular.v42.v2beta1.json @@ -449,7 +449,29 @@ "group": "table", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "table", + "originalOptions": { + "styles": [ + { + "colors": [ + "red", + "yellow", + "green" + ], + "pattern": "/.*/", + "thresholds": [ + "10", + "20" + ], + "unit": "short" + } + ], + "table": "table2" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2alpha1.json index d15f179ac31..c7298296064 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2alpha1.json @@ -110,7 +110,15 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "# Angular Text Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2beta1.json index 59aad2fdb0b..db2cfcb916e 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v26.text2_to_text.v42.v2beta1.json @@ -115,7 +115,15 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "# Angular Text Panel\n# $constant\n\nFor markdown syntax help: [commonmark.org/help](https://commonmark.org/help/)\n\n## $text\n\n", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json index 6eb2f2493aa..4a9e24d85f6 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2alpha1.json @@ -361,7 +361,15 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "## Data link variables overview\n\nThis dashboard presents variables that one can use when creating *data links*. All links redirect to this dashboard and this panel represents the values that were interpolated in the link that was clicked.\n\n\n#### Series variables\n1. **Name:** \u003cspan style=\"color: orange;\"\u003e$seriesName\u003c/span\u003e\n2. **label.datacenter:** \u003cspan style=\"color: orange;\"\u003e$labelDatacenter\u003c/span\u003e\n3. **label.datacenter.region:** \u003cspan style=\"color: orange;\"\u003e$labelDatacenterRegion\u003c/span\u003e\n\n#### Field variables\n1. **Name:** \u003cspan style=\"color: orange;\"\u003e$fieldName\u003c/span\u003e\n\n#### Value variables\n1. **Time:** \u003cspan style=\"color: orange;\"\u003e$valueTime\u003c/span\u003e\n2. **Numeric:** \u003cspan style=\"color: orange;\"\u003e$valueNumeric\u003c/span\u003e\n3. **Text:** \u003cspan style=\"color: orange;\"\u003e$valueText\u003c/span\u003e\n4. **Calc:** \u003cspan style=\"color: orange;\"\u003e$valueCalc\u003c/span\u003e\n\n", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json index 22814d61a1e..62b30aa6480 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-datalinks.v42.v2beta1.json @@ -372,7 +372,15 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "## Data link variables overview\n\nThis dashboard presents variables that one can use when creating *data links*. All links redirect to this dashboard and this panel represents the values that were interpolated in the link that was clicked.\n\n\n#### Series variables\n1. **Name:** \u003cspan style=\"color: orange;\"\u003e$seriesName\u003c/span\u003e\n2. **label.datacenter:** \u003cspan style=\"color: orange;\"\u003e$labelDatacenter\u003c/span\u003e\n3. **label.datacenter.region:** \u003cspan style=\"color: orange;\"\u003e$labelDatacenterRegion\u003c/span\u003e\n\n#### Field variables\n1. **Name:** \u003cspan style=\"color: orange;\"\u003e$fieldName\u003c/span\u003e\n\n#### Value variables\n1. **Time:** \u003cspan style=\"color: orange;\"\u003e$valueTime\u003c/span\u003e\n2. **Numeric:** \u003cspan style=\"color: orange;\"\u003e$valueNumeric\u003c/span\u003e\n3. **Text:** \u003cspan style=\"color: orange;\"\u003e$valueText\u003c/span\u003e\n4. **Calc:** \u003cspan style=\"color: orange;\"\u003e$valueCalc\u003c/span\u003e\n\n", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json index a469d4020e5..f754cbd766a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2alpha1.json @@ -167,7 +167,15 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "## Data center = $datacenter\n\n### server = $server\n\n#### pod = $pod", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json index 0a46a76d48e..8a8cffdf465 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables-drilldown.v42.v2beta1.json @@ -174,7 +174,15 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "## Data center = $datacenter\n\n### server = $server\n\n#### pod = $pod", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json index ecc156114f4..89857905689 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json @@ -273,7 +273,15 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "## Data center = $datacenter\n\n### server = $server\n\n#### pod = $pod", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json index 54d6e9efa1c..13320b47904 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json @@ -282,7 +282,15 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "## Data center = $datacenter\n\n### server = $server\n\n#### pod = $pod", + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v1beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v1beta1.json index 5ae52076acc..7542cf17652 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v1beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v1beta1.json @@ -296,6 +296,7 @@ } }, { + "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", "datasource": { "type": "grafana-testdata-datasource" }, @@ -306,15 +307,7 @@ "y": 0 }, "id": 6, - "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", - "mode": "markdown" - }, + "mode": "markdown", "pluginVersion": "11.0.0-pre", "targets": [ { diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json index 3c2a1a4f9d7..21d45e91f10 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2alpha1.json @@ -1256,13 +1256,13 @@ "spec": { "pluginVersion": "11.0.0-pre", "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", - "mode": "markdown" + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", + "mode": "markdown" + } + } }, "fieldConfig": { "defaults": {}, diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json index c6a9186b9e0..ed7123f5e38 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/migrations/v0alpha1.migrations.v42.v2beta1.json @@ -1301,13 +1301,13 @@ "version": "11.0.0-pre", "spec": { "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", - "mode": "markdown" + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", + "mode": "markdown" + } + } }, "fieldConfig": { "defaults": {}, diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2alpha1.json index 77874ee6291..fe4271d08e7 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2alpha1.json @@ -62,6 +62,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -151,6 +157,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -241,6 +253,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -320,6 +338,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -401,6 +425,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -480,6 +510,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -559,6 +595,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -639,6 +681,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -730,6 +778,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -927,6 +981,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -1006,6 +1066,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -1085,6 +1151,12 @@ "spec": { "pluginVersion": "7.4.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2beta1.json index 663abfbf74c..261cd5228db 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests.v42.v2beta1.json @@ -67,6 +67,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -159,6 +165,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -252,6 +264,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -334,6 +352,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -418,6 +442,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -500,6 +530,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -582,6 +618,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -665,6 +707,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -759,6 +807,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -961,6 +1015,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -1043,6 +1103,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ @@ -1125,6 +1191,12 @@ "version": "7.4.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "gauge", + "originalOptions": { + "nullPointMode": "null" + } + }, "baseColor": "#299c46", "reduceOptions": { "calcs": [ diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json index f93a54ad34d..fa1ae3a63d8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2alpha1.json @@ -412,7 +412,17 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Should be a long line connecting the null region in the `connected` mode, and in zero it should just be a line with zero value at the null points. ", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -456,7 +466,17 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Stacking values on top of nulls, should treat the null values as zero. ", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -500,7 +520,17 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Stacking when all values are null should leave a gap in the graph", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1681,7 +1711,17 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Left is showing null between values for a normal line graph and staircase graph. Orphaned data points should be rendered as points", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -2061,7 +2101,17 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Just verify that the tooltip time has millisecond resolution ", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -2105,7 +2155,17 @@ "kind": "text", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Verify that axis labels look ok", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json index d611243ea1d..5a580bbaa59 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-graph/v0alpha1.graph_tests.v42.v2beta1.json @@ -429,7 +429,17 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Should be a long line connecting the null region in the `connected` mode, and in zero it should just be a line with zero value at the null points. ", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -475,7 +485,17 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Stacking values on top of nulls, should treat the null values as zero. ", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -521,7 +541,17 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Stacking when all values are null should leave a gap in the graph", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -1779,7 +1809,17 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Left is showing null between values for a normal line graph and staircase graph. Orphaned data points should be rendered as points", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -2172,7 +2212,17 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Just verify that the tooltip time has millisecond resolution ", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -2218,7 +2268,17 @@ "group": "text", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "text", + "originalOptions": { + "content": "Verify that axis labels look ok", + "editable": true, + "error": false, + "mode": "markdown" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2alpha1.json index 4873fe5fc74..8225aec6834 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2alpha1.json @@ -74,7 +74,44 @@ "kind": "heatmap", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateViridis", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "tooltipDecimals": 4, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 2, + "format": "areaM2", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -116,7 +153,46 @@ "kind": "heatmap", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": { + "cardRound": 50 + }, + "color": { + "cardColor": "#1F60C4", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": false + }, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 1, + "format": "kwatt", + "logBase": 1, + "show": true, + "width": "100" + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -158,7 +234,44 @@ "kind": "heatmap", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#1F60C4", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": true, + "tooltip": { + "show": true, + "showHistogram": false + }, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 1, + "format": "kwatt", + "logBase": 1, + "show": true, + "width": "100" + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -204,7 +317,46 @@ "kind": "heatmap", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateViridis", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "tooltipDecimals": 4, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 2, + "format": "areaM2", + "logBase": 1, + "max": "50", + "min": "20", + "show": true + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -246,7 +398,44 @@ "kind": "heatmap", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateBuGn", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "xAxis": { + "show": true + }, + "xBucketNumber": 10, + "yAxis": { + "format": "short", + "logBase": 2, + "show": true, + "splitFactor": 2 + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -288,7 +477,44 @@ "kind": "heatmap", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateBuGn", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "xAxis": { + "show": true + }, + "xBucketNumber": 10, + "yAxis": { + "format": "short", + "logBase": 10, + "show": true, + "splitFactor": 5 + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2beta1.json index 2e11457ec4d..d7ec2abb5e8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-heatmap/v0alpha1.heatmap-legacy.v42.v2beta1.json @@ -78,7 +78,44 @@ "group": "heatmap", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateViridis", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "tooltipDecimals": 4, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 2, + "format": "areaM2", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -123,7 +160,46 @@ "group": "heatmap", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": { + "cardRound": 50 + }, + "color": { + "cardColor": "#1F60C4", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": false + }, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 1, + "format": "kwatt", + "logBase": 1, + "show": true, + "width": "100" + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -168,7 +244,44 @@ "group": "heatmap", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#1F60C4", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": true, + "tooltip": { + "show": true, + "showHistogram": false + }, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 1, + "format": "kwatt", + "logBase": 1, + "show": true, + "width": "100" + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -216,7 +329,46 @@ "group": "heatmap", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateViridis", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "tooltipDecimals": 4, + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 2, + "format": "areaM2", + "logBase": 1, + "max": "50", + "min": "20", + "show": true + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -261,7 +413,44 @@ "group": "heatmap", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateBuGn", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "xAxis": { + "show": true + }, + "xBucketNumber": 10, + "yAxis": { + "format": "short", + "logBase": 2, + "show": true, + "splitFactor": 2 + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -306,7 +495,44 @@ "group": "heatmap", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateBuGn", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "legend": { + "show": true + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": true + }, + "xAxis": { + "show": true + }, + "xBucketNumber": 10, + "yAxis": { + "format": "short", + "logBase": 10, + "show": true, + "splitFactor": 5 + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2alpha1.json index 68c7c2b6529..636612934c4 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2alpha1.json @@ -119,7 +119,1073 @@ "kind": "grafana-polystat-panel", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-polystat-panel", + "originalOptions": { + "animationModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "d3DivId": "d3_svg_2", + "decimals": 2, + "displayModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "fontSizes": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, + 48, + 50, + 52, + 54, + 56, + 58, + 60, + 62, + 64, + 66, + 68, + 70 + ], + "fontTypes": [ + "Open Sans", + "Arial", + "Avant Garde", + "Bookman", + "Consolas", + "Courier", + "Courier New", + "Futura", + "Garamond", + "Helvetica", + "Palatino", + "Times", + "Times New Roman", + "Verdana" + ], + "format": "none", + "notcolors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "operatorName": "avg", + "operatorOptions": [ + { + "text": "Average", + "value": "avg" + }, + { + "text": "Count", + "value": "count" + }, + { + "text": "Current", + "value": "current" + }, + { + "text": "Delta", + "value": "delta" + }, + { + "text": "Difference", + "value": "diff" + }, + { + "text": "First", + "value": "first" + }, + { + "text": "Log Min", + "value": "logmin" + }, + { + "text": "Max", + "value": "max" + }, + { + "text": "Min", + "value": "min" + }, + { + "text": "Name", + "value": "name" + }, + { + "text": "Time of Last Point", + "value": "last_time" + }, + { + "text": "Time Step", + "value": "time_step" + }, + { + "text": "Total", + "value": "total" + } + ], + "polystat": { + "animationSpeed": 2500, + "columnAutoSize": true, + "columns": 1, + "defaultClickThrough": "", + "defaultClickThroughSanitize": true, + "displayLimit": 100, + "fontAutoScale": true, + "fontSize": 12, + "globalDisplayMode": "all", + "globalOperatorName": "avg", + "gradientEnabled": true, + "hexagonSortByDirection": "asc", + "hexagonSortByField": "name", + "maxMetrics": 0, + "polygonBorderColor": "black", + "polygonBorderSize": 2, + "radius": "", + "radiusAutoSize": true, + "rowAutoSize": true, + "rows": 1, + "shape": "hexagon_pointed_top", + "tooltipDisplayMode": "all", + "tooltipDisplayTextTriggeredEmpty": "OK", + "tooltipFontSize": 12, + "tooltipFontType": "Open Sans", + "tooltipPrimarySortDirection": "desc", + "tooltipPrimarySortField": "thresholdLevel", + "tooltipSecondarySortDirection": "desc", + "tooltipSecondarySortField": "value", + "tooltipTimestampEnabled": true + }, + "savedComposites": [], + "savedOverrides": [], + "shapes": [ + { + "text": "Hexagon Pointed Top", + "value": "hexagon_pointed_top" + }, + { + "text": "Hexagon Flat Top", + "value": "hexagon_flat_top" + }, + { + "text": "Circle", + "value": "circle" + }, + { + "text": "Cross", + "value": "cross" + }, + { + "text": "Diamond", + "value": "diamond" + }, + { + "text": "Square", + "value": "square" + }, + { + "text": "Star", + "value": "star" + }, + { + "text": "Triangle", + "value": "triangle" + }, + { + "text": "Wye", + "value": "wye" + } + ], + "sortDirections": [ + { + "text": "Ascending", + "value": "asc" + }, + { + "text": "Descending", + "value": "desc" + } + ], + "sortFields": [ + { + "text": "Name", + "value": "name" + }, + { + "text": "Threshold Level", + "value": "thresholdLevel" + }, + { + "text": "Value", + "value": "value" + } + ], + "svgContainer": {}, + "thresholdStates": [ + { + "text": "ok", + "value": 0 + }, + { + "text": "warning", + "value": 1 + }, + { + "text": "critical", + "value": 2 + }, + { + "text": "custom", + "value": 3 + } + ], + "unitFormats": [ + { + "submenu": [ + { + "text": "none", + "value": "none" + }, + { + "text": "short", + "value": "short" + }, + { + "text": "percent (0-100)", + "value": "percent" + }, + { + "text": "percent (0.0-1.0)", + "value": "percentunit" + }, + { + "text": "Humidity (%H)", + "value": "humidity" + }, + { + "text": "decibel", + "value": "dB" + }, + { + "text": "hexadecimal (0x)", + "value": "hex0x" + }, + { + "text": "hexadecimal", + "value": "hex" + }, + { + "text": "scientific notation", + "value": "sci" + }, + { + "text": "locale format", + "value": "locale" + } + ], + "text": "none" + }, + { + "submenu": [ + { + "text": "Dollars ($)", + "value": "currencyUSD" + }, + { + "text": "Pounds (£)", + "value": "currencyGBP" + }, + { + "text": "Euro (€)", + "value": "currencyEUR" + }, + { + "text": "Yen (¥)", + "value": "currencyJPY" + }, + { + "text": "Rubles (₽)", + "value": "currencyRUB" + }, + { + "text": "Hryvnias (₴)", + "value": "currencyUAH" + }, + { + "text": "Real (R$)", + "value": "currencyBRL" + }, + { + "text": "Danish Krone (kr)", + "value": "currencyDKK" + }, + { + "text": "Icelandic Króna (kr)", + "value": "currencyISK" + }, + { + "text": "Norwegian Krone (kr)", + "value": "currencyNOK" + }, + { + "text": "Swedish Krona (kr)", + "value": "currencySEK" + }, + { + "text": "Czech koruna (czk)", + "value": "currencyCZK" + }, + { + "text": "Swiss franc (CHF)", + "value": "currencyCHF" + }, + { + "text": "Polish Złoty (PLN)", + "value": "currencyPLN" + }, + { + "text": "Bitcoin (฿)", + "value": "currencyBTC" + }, + { + "text": "Vietnamese Dong (VND)", + "value": "currencyVND" + }, + { + "text": "Malaysian Ringgit (RM)", + "value": "currencyMYR" + }, + { + "text": "Bulgarian Lev (BGN)", + "value": "currencyBGN" + } + ], + "text": "currency" + }, + { + "submenu": [ + { + "text": "Hertz (1/s)", + "value": "hertz" + }, + { + "text": "nanoseconds (ns)", + "value": "ns" + }, + { + "text": "microseconds (µs)", + "value": "µs" + }, + { + "text": "milliseconds (ms)", + "value": "ms" + }, + { + "text": "seconds (s)", + "value": "s" + }, + { + "text": "minutes (m)", + "value": "m" + }, + { + "text": "hours (h)", + "value": "h" + }, + { + "text": "days (d)", + "value": "d" + }, + { + "text": "duration (ms)", + "value": "dtdurationms" + }, + { + "text": "duration (s)", + "value": "dtdurations" + }, + { + "text": "duration (hh:mm:ss)", + "value": "dthms" + }, + { + "text": "Timeticks (s/100)", + "value": "timeticks" + } + ], + "text": "time" + }, + { + "submenu": [ + { + "text": "YYYY-MM-DD HH:mm:ss", + "value": "dateTimeAsIso" + }, + { + "text": "DD/MM/YYYY h:mm:ss a", + "value": "dateTimeAsUS" + }, + { + "text": "From Now", + "value": "dateTimeFromNow" + } + ], + "text": "date \u0026 time" + }, + { + "submenu": [ + { + "text": "bits", + "value": "bits" + }, + { + "text": "bytes", + "value": "bytes" + }, + { + "text": "kibibytes", + "value": "kbytes" + }, + { + "text": "mebibytes", + "value": "mbytes" + }, + { + "text": "gibibytes", + "value": "gbytes" + } + ], + "text": "data (IEC)" + }, + { + "submenu": [ + { + "text": "bits", + "value": "decbits" + }, + { + "text": "bytes", + "value": "decbytes" + }, + { + "text": "kilobytes", + "value": "deckbytes" + }, + { + "text": "megabytes", + "value": "decmbytes" + }, + { + "text": "gigabytes", + "value": "decgbytes" + } + ], + "text": "data (Metric)" + }, + { + "submenu": [ + { + "text": "packets/sec", + "value": "pps" + }, + { + "text": "bits/sec", + "value": "bps" + }, + { + "text": "bytes/sec", + "value": "Bps" + }, + { + "text": "kilobits/sec", + "value": "Kbits" + }, + { + "text": "kilobytes/sec", + "value": "KBs" + }, + { + "text": "megabits/sec", + "value": "Mbits" + }, + { + "text": "megabytes/sec", + "value": "MBs" + }, + { + "text": "gigabytes/sec", + "value": "GBs" + }, + { + "text": "gigabits/sec", + "value": "Gbits" + } + ], + "text": "data rate" + }, + { + "submenu": [ + { + "text": "hashes/sec", + "value": "Hs" + }, + { + "text": "kilohashes/sec", + "value": "KHs" + }, + { + "text": "megahashes/sec", + "value": "MHs" + }, + { + "text": "gigahashes/sec", + "value": "GHs" + }, + { + "text": "terahashes/sec", + "value": "THs" + }, + { + "text": "petahashes/sec", + "value": "PHs" + }, + { + "text": "exahashes/sec", + "value": "EHs" + } + ], + "text": "hash rate" + }, + { + "submenu": [ + { + "text": "ops/sec (ops)", + "value": "ops" + }, + { + "text": "requests/sec (rps)", + "value": "reqps" + }, + { + "text": "reads/sec (rps)", + "value": "rps" + }, + { + "text": "writes/sec (wps)", + "value": "wps" + }, + { + "text": "I/O ops/sec (iops)", + "value": "iops" + }, + { + "text": "ops/min (opm)", + "value": "opm" + }, + { + "text": "requests/min (rpm)", + "value": "reqpm" + }, + { + "text": "reads/min (rpm)", + "value": "rpm" + }, + { + "text": "writes/min (wpm)", + "value": "wpm" + } + ], + "text": "throughput" + }, + { + "submenu": [ + { + "text": "millimetre (mm)", + "value": "lengthmm" + }, + { + "text": "meter (m)", + "value": "lengthm" + }, + { + "text": "inch (in)", + "value": "lengthin" + }, + { + "text": "feet (ft)", + "value": "lengthft" + }, + { + "text": "kilometer (km)", + "value": "lengthkm" + }, + { + "text": "mile (mi)", + "value": "lengthmi" + } + ], + "text": "length" + }, + { + "submenu": [ + { + "text": "Square Meters (m²)", + "value": "areaM2" + }, + { + "text": "Square Feet (ft²)", + "value": "areaF2" + }, + { + "text": "Square Miles (mi²)", + "value": "areaMI2" + } + ], + "text": "area" + }, + { + "submenu": [ + { + "text": "milligram (mg)", + "value": "massmg" + }, + { + "text": "gram (g)", + "value": "massg" + }, + { + "text": "pound (lb)", + "value": "masslb" + }, + { + "text": "kilogram (kg)", + "value": "masskg" + }, + { + "text": "metric ton (t)", + "value": "masst" + } + ], + "text": "mass" + }, + { + "submenu": [ + { + "text": "metres/second (m/s)", + "value": "velocityms" + }, + { + "text": "kilometers/hour (km/h)", + "value": "velocitykmh" + }, + { + "text": "miles/hour (mph)", + "value": "velocitymph" + }, + { + "text": "knot (kn)", + "value": "velocityknot" + } + ], + "text": "velocity" + }, + { + "submenu": [ + { + "text": "millilitre (mL)", + "value": "mlitre" + }, + { + "text": "litre (L)", + "value": "litre" + }, + { + "text": "cubic metre", + "value": "m3" + }, + { + "text": "Normal cubic metre", + "value": "Nm3" + }, + { + "text": "cubic decimetre", + "value": "dm3" + }, + { + "text": "gallons", + "value": "gallons" + } + ], + "text": "volume" + }, + { + "submenu": [ + { + "text": "Watt (W)", + "value": "watt" + }, + { + "text": "Kilowatt (kW)", + "value": "kwatt" + }, + { + "text": "Milliwatt (mW)", + "value": "mwatt" + }, + { + "text": "Watt per square metre (W/m²)", + "value": "Wm2" + }, + { + "text": "Volt-ampere (VA)", + "value": "voltamp" + }, + { + "text": "Kilovolt-ampere (kVA)", + "value": "kvoltamp" + }, + { + "text": "Volt-ampere reactive (var)", + "value": "voltampreact" + }, + { + "text": "Kilovolt-ampere reactive (kvar)", + "value": "kvoltampreact" + }, + { + "text": "Watt-hour (Wh)", + "value": "watth" + }, + { + "text": "Kilowatt-hour (kWh)", + "value": "kwatth" + }, + { + "text": "Kilowatt-min (kWm)", + "value": "kwattm" + }, + { + "text": "Joule (J)", + "value": "joule" + }, + { + "text": "Electron volt (eV)", + "value": "ev" + }, + { + "text": "Ampere (A)", + "value": "amp" + }, + { + "text": "Kiloampere (kA)", + "value": "kamp" + }, + { + "text": "Milliampere (mA)", + "value": "mamp" + }, + { + "text": "Volt (V)", + "value": "volt" + }, + { + "text": "Kilovolt (kV)", + "value": "kvolt" + }, + { + "text": "Millivolt (mV)", + "value": "mvolt" + }, + { + "text": "Decibel-milliwatt (dBm)", + "value": "dBm" + }, + { + "text": "Ohm (Ω)", + "value": "ohm" + }, + { + "text": "Lumens (Lm)", + "value": "lumens" + } + ], + "text": "energy" + }, + { + "submenu": [ + { + "text": "Celsius (°C)", + "value": "celsius" + }, + { + "text": "Fahrenheit (°F)", + "value": "fahrenheit" + }, + { + "text": "Kelvin (K)", + "value": "kelvin" + } + ], + "text": "temperature" + }, + { + "submenu": [ + { + "text": "Millibars", + "value": "pressurembar" + }, + { + "text": "Bars", + "value": "pressurebar" + }, + { + "text": "Kilobars", + "value": "pressurekbar" + }, + { + "text": "Hectopascals", + "value": "pressurehpa" + }, + { + "text": "Kilopascals", + "value": "pressurekpa" + }, + { + "text": "Inches of mercury", + "value": "pressurehg" + }, + { + "text": "PSI", + "value": "pressurepsi" + } + ], + "text": "pressure" + }, + { + "submenu": [ + { + "text": "Newton-meters (Nm)", + "value": "forceNm" + }, + { + "text": "Kilonewton-meters (kNm)", + "value": "forcekNm" + }, + { + "text": "Newtons (N)", + "value": "forceN" + }, + { + "text": "Kilonewtons (kN)", + "value": "forcekN" + } + ], + "text": "force" + }, + { + "submenu": [ + { + "text": "Gallons/min (gpm)", + "value": "flowgpm" + }, + { + "text": "Cubic meters/sec (cms)", + "value": "flowcms" + }, + { + "text": "Cubic feet/sec (cfs)", + "value": "flowcfs" + }, + { + "text": "Cubic feet/min (cfm)", + "value": "flowcfm" + }, + { + "text": "Litre/hour", + "value": "litreh" + }, + { + "text": "Litre/min (l/min)", + "value": "flowlpm" + }, + { + "text": "milliLitre/min (mL/min)", + "value": "flowmlpm" + } + ], + "text": "flow" + }, + { + "submenu": [ + { + "text": "Degrees (°)", + "value": "degree" + }, + { + "text": "Radians", + "value": "radian" + }, + { + "text": "Gradian", + "value": "grad" + } + ], + "text": "angle" + }, + { + "submenu": [ + { + "text": "Meters/sec²", + "value": "accMS2" + }, + { + "text": "Feet/sec²", + "value": "accFS2" + }, + { + "text": "G unit", + "value": "accG" + } + ], + "text": "acceleration" + }, + { + "submenu": [ + { + "text": "Becquerel (Bq)", + "value": "radbq" + }, + { + "text": "curie (Ci)", + "value": "radci" + }, + { + "text": "Gray (Gy)", + "value": "radgy" + }, + { + "text": "rad", + "value": "radrad" + }, + { + "text": "Sievert (Sv)", + "value": "radsv" + }, + { + "text": "rem", + "value": "radrem" + }, + { + "text": "Exposure (C/kg)", + "value": "radexpckg" + }, + { + "text": "roentgen (R)", + "value": "radr" + }, + { + "text": "Sievert/hour (Sv/h)", + "value": "radsvh" + } + ], + "text": "radiation" + }, + { + "submenu": [ + { + "text": "parts-per-million (ppm)", + "value": "ppm" + }, + { + "text": "parts-per-billion (ppb)", + "value": "conppb" + }, + { + "text": "nanogram per cubic metre (ng/m³)", + "value": "conngm3" + }, + { + "text": "nanogram per normal cubic metre (ng/Nm³)", + "value": "conngNm3" + }, + { + "text": "microgram per cubic metre (μg/m³)", + "value": "conμgm3" + }, + { + "text": "microgram per normal cubic metre (μg/Nm³)", + "value": "conμgNm3" + }, + { + "text": "milligram per cubic metre (mg/m³)", + "value": "conmgm3" + }, + { + "text": "milligram per normal cubic metre (mg/Nm³)", + "value": "conmgNm3" + }, + { + "text": "gram per cubic metre (g/m³)", + "value": "congm3" + }, + { + "text": "gram per normal cubic metre (g/Nm³)", + "value": "congNm3" + } + ], + "text": "concentration" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -228,7 +1294,1129 @@ "kind": "grafana-polystat-panel", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-polystat-panel", + "originalOptions": { + "animationModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "d3DivId": "d3_svg_4", + "decimals": 2, + "displayModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "fontSizes": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, + 48, + 50, + 52, + 54, + 56, + 58, + 60, + 62, + 64, + 66, + 68, + 70 + ], + "fontTypes": [ + "Open Sans", + "Arial", + "Avant Garde", + "Bookman", + "Consolas", + "Courier", + "Courier New", + "Futura", + "Garamond", + "Helvetica", + "Palatino", + "Times", + "Times New Roman", + "Verdana" + ], + "format": "none", + "notcolors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "operatorName": "avg", + "operatorOptions": [ + { + "text": "Average", + "value": "avg" + }, + { + "text": "Count", + "value": "count" + }, + { + "text": "Current", + "value": "current" + }, + { + "text": "Delta", + "value": "delta" + }, + { + "text": "Difference", + "value": "diff" + }, + { + "text": "First", + "value": "first" + }, + { + "text": "Log Min", + "value": "logmin" + }, + { + "text": "Max", + "value": "max" + }, + { + "text": "Min", + "value": "min" + }, + { + "text": "Name", + "value": "name" + }, + { + "text": "Time of Last Point", + "value": "last_time" + }, + { + "text": "Time Step", + "value": "time_step" + }, + { + "text": "Total", + "value": "total" + } + ], + "polystat": { + "animationSpeed": 2500, + "columnAutoSize": true, + "columns": "", + "defaultClickThrough": "", + "defaultClickThroughSanitize": true, + "displayLimit": 100, + "fontAutoScale": true, + "fontSize": 12, + "globalDisplayMode": "all", + "globalOperatorName": "avg", + "gradientEnabled": true, + "hexagonSortByDirection": "asc", + "hexagonSortByField": "name", + "maxMetrics": 0, + "polygonBorderColor": "black", + "polygonBorderSize": 2, + "radius": "", + "radiusAutoSize": true, + "rowAutoSize": true, + "rows": "", + "shape": "hexagon_pointed_top", + "tooltipDisplayMode": "all", + "tooltipDisplayTextTriggeredEmpty": "OK", + "tooltipFontSize": 12, + "tooltipFontType": "Open Sans", + "tooltipPrimarySortDirection": "desc", + "tooltipPrimarySortField": "thresholdLevel", + "tooltipSecondarySortDirection": "desc", + "tooltipSecondarySortField": "value", + "tooltipTimestampEnabled": true + }, + "savedComposites": [], + "savedOverrides": [], + "shapes": [ + { + "text": "Hexagon Pointed Top", + "value": "hexagon_pointed_top" + }, + { + "text": "Hexagon Flat Top", + "value": "hexagon_flat_top" + }, + { + "text": "Circle", + "value": "circle" + }, + { + "text": "Cross", + "value": "cross" + }, + { + "text": "Diamond", + "value": "diamond" + }, + { + "text": "Square", + "value": "square" + }, + { + "text": "Star", + "value": "star" + }, + { + "text": "Triangle", + "value": "triangle" + }, + { + "text": "Wye", + "value": "wye" + } + ], + "sortDirections": [ + { + "text": "Ascending", + "value": "asc" + }, + { + "text": "Descending", + "value": "desc" + } + ], + "sortFields": [ + { + "text": "Name", + "value": "name" + }, + { + "text": "Threshold Level", + "value": "thresholdLevel" + }, + { + "text": "Value", + "value": "value" + } + ], + "svgContainer": {}, + "thresholdStates": [ + { + "text": "ok", + "value": 0 + }, + { + "text": "warning", + "value": 1 + }, + { + "text": "critical", + "value": 2 + }, + { + "text": "custom", + "value": 3 + } + ], + "unitFormats": [ + { + "submenu": [ + { + "text": "none", + "value": "none" + }, + { + "text": "short", + "value": "short" + }, + { + "text": "percent (0-100)", + "value": "percent" + }, + { + "text": "percent (0.0-1.0)", + "value": "percentunit" + }, + { + "text": "Humidity (%H)", + "value": "humidity" + }, + { + "text": "decibel", + "value": "dB" + }, + { + "text": "hexadecimal (0x)", + "value": "hex0x" + }, + { + "text": "hexadecimal", + "value": "hex" + }, + { + "text": "scientific notation", + "value": "sci" + }, + { + "text": "locale format", + "value": "locale" + } + ], + "text": "none" + }, + { + "submenu": [ + { + "text": "Dollars ($)", + "value": "currencyUSD" + }, + { + "text": "Pounds (£)", + "value": "currencyGBP" + }, + { + "text": "Euro (€)", + "value": "currencyEUR" + }, + { + "text": "Yen (¥)", + "value": "currencyJPY" + }, + { + "text": "Rubles (₽)", + "value": "currencyRUB" + }, + { + "text": "Hryvnias (₴)", + "value": "currencyUAH" + }, + { + "text": "Real (R$)", + "value": "currencyBRL" + }, + { + "text": "Danish Krone (kr)", + "value": "currencyDKK" + }, + { + "text": "Icelandic Króna (kr)", + "value": "currencyISK" + }, + { + "text": "Norwegian Krone (kr)", + "value": "currencyNOK" + }, + { + "text": "Swedish Krona (kr)", + "value": "currencySEK" + }, + { + "text": "Czech koruna (czk)", + "value": "currencyCZK" + }, + { + "text": "Swiss franc (CHF)", + "value": "currencyCHF" + }, + { + "text": "Polish Złoty (PLN)", + "value": "currencyPLN" + }, + { + "text": "Bitcoin (฿)", + "value": "currencyBTC" + }, + { + "text": "Milli Bitcoin (mBTC)", + "value": "currencymBTC" + }, + { + "text": "Micro Bitcoin (μBTC)", + "value": "currencyμBTC" + }, + { + "text": "Vietnamese Dong (VND)", + "value": "currencyVND" + }, + { + "text": "Turkish Lira (₺)", + "value": "currencyTRY" + }, + { + "text": "Malaysian Ringgit (RM)", + "value": "currencyMYR" + }, + { + "text": "CFP franc (XPF)", + "value": "currencyXPF" + }, + { + "text": "Bulgarian Lev (BGN)", + "value": "currencyBGN" + } + ], + "text": "currency" + }, + { + "submenu": [ + { + "text": "Hertz (1/s)", + "value": "hertz" + }, + { + "text": "nanoseconds (ns)", + "value": "ns" + }, + { + "text": "microseconds (µs)", + "value": "µs" + }, + { + "text": "milliseconds (ms)", + "value": "ms" + }, + { + "text": "seconds (s)", + "value": "s" + }, + { + "text": "minutes (m)", + "value": "m" + }, + { + "text": "hours (h)", + "value": "h" + }, + { + "text": "days (d)", + "value": "d" + }, + { + "text": "duration (ms)", + "value": "dtdurationms" + }, + { + "text": "duration (s)", + "value": "dtdurations" + }, + { + "text": "duration (hh:mm:ss)", + "value": "dthms" + }, + { + "text": "Timeticks (s/100)", + "value": "timeticks" + } + ], + "text": "time" + }, + { + "submenu": [ + { + "text": "YYYY-MM-DD HH:mm:ss", + "value": "dateTimeAsIso" + }, + { + "text": "DD/MM/YYYY h:mm:ss a", + "value": "dateTimeAsUS" + }, + { + "text": "From Now", + "value": "dateTimeFromNow" + } + ], + "text": "date \u0026 time" + }, + { + "submenu": [ + { + "text": "bits", + "value": "bits" + }, + { + "text": "bytes", + "value": "bytes" + }, + { + "text": "kibibytes", + "value": "kbytes" + }, + { + "text": "mebibytes", + "value": "mbytes" + }, + { + "text": "gibibytes", + "value": "gbytes" + } + ], + "text": "data (IEC)" + }, + { + "submenu": [ + { + "text": "bits", + "value": "decbits" + }, + { + "text": "bytes", + "value": "decbytes" + }, + { + "text": "kilobytes", + "value": "deckbytes" + }, + { + "text": "megabytes", + "value": "decmbytes" + }, + { + "text": "gigabytes", + "value": "decgbytes" + } + ], + "text": "data (Metric)" + }, + { + "submenu": [ + { + "text": "packets/sec", + "value": "pps" + }, + { + "text": "bits/sec", + "value": "bps" + }, + { + "text": "bytes/sec", + "value": "Bps" + }, + { + "text": "kilobits/sec", + "value": "Kbits" + }, + { + "text": "kilobytes/sec", + "value": "KBs" + }, + { + "text": "megabits/sec", + "value": "Mbits" + }, + { + "text": "megabytes/sec", + "value": "MBs" + }, + { + "text": "gigabytes/sec", + "value": "GBs" + }, + { + "text": "gigabits/sec", + "value": "Gbits" + } + ], + "text": "data rate" + }, + { + "submenu": [ + { + "text": "hashes/sec", + "value": "Hs" + }, + { + "text": "kilohashes/sec", + "value": "KHs" + }, + { + "text": "megahashes/sec", + "value": "MHs" + }, + { + "text": "gigahashes/sec", + "value": "GHs" + }, + { + "text": "terahashes/sec", + "value": "THs" + }, + { + "text": "petahashes/sec", + "value": "PHs" + }, + { + "text": "exahashes/sec", + "value": "EHs" + } + ], + "text": "hash rate" + }, + { + "submenu": [ + { + "text": "counts/sec (cps)", + "value": "cps" + }, + { + "text": "ops/sec (ops)", + "value": "ops" + }, + { + "text": "requests/sec (rps)", + "value": "reqps" + }, + { + "text": "reads/sec (rps)", + "value": "rps" + }, + { + "text": "writes/sec (wps)", + "value": "wps" + }, + { + "text": "I/O ops/sec (iops)", + "value": "iops" + }, + { + "text": "events/sec (eps)", + "value": "eps" + }, + { + "text": "messages/sec (mps)", + "value": "mps" + }, + { + "text": "records/sec (rps)", + "value": "recps" + }, + { + "text": "rows/sec (rps)", + "value": "rowsps" + }, + { + "text": "counts/min (cpm)", + "value": "cpm" + }, + { + "text": "ops/min (opm)", + "value": "opm" + }, + { + "text": "requests/min (rpm)", + "value": "reqpm" + }, + { + "text": "reads/min (rpm)", + "value": "rpm" + }, + { + "text": "writes/min (wpm)", + "value": "wpm" + }, + { + "text": "events/min (epm)", + "value": "epm" + }, + { + "text": "messages/min (mpm)", + "value": "mpm" + }, + { + "text": "records/min (rpm)", + "value": "recpm" + }, + { + "text": "rows/min (rpm)", + "value": "rowspm" + } + ], + "text": "throughput" + }, + { + "submenu": [ + { + "text": "millimetre (mm)", + "value": "lengthmm" + }, + { + "text": "meter (m)", + "value": "lengthm" + }, + { + "text": "inch (in)", + "value": "lengthin" + }, + { + "text": "feet (ft)", + "value": "lengthft" + }, + { + "text": "kilometer (km)", + "value": "lengthkm" + }, + { + "text": "mile (mi)", + "value": "lengthmi" + } + ], + "text": "length" + }, + { + "submenu": [ + { + "text": "Square Meters (m²)", + "value": "areaM2" + }, + { + "text": "Square Feet (ft²)", + "value": "areaF2" + }, + { + "text": "Square Miles (mi²)", + "value": "areaMI2" + } + ], + "text": "area" + }, + { + "submenu": [ + { + "text": "milligram (mg)", + "value": "massmg" + }, + { + "text": "gram (g)", + "value": "massg" + }, + { + "text": "pound (lb)", + "value": "masslb" + }, + { + "text": "kilogram (kg)", + "value": "masskg" + }, + { + "text": "metric ton (t)", + "value": "masst" + } + ], + "text": "mass" + }, + { + "submenu": [ + { + "text": "metres/second (m/s)", + "value": "velocityms" + }, + { + "text": "kilometers/hour (km/h)", + "value": "velocitykmh" + }, + { + "text": "miles/hour (mph)", + "value": "velocitymph" + }, + { + "text": "knot (kn)", + "value": "velocityknot" + } + ], + "text": "velocity" + }, + { + "submenu": [ + { + "text": "millilitre (mL)", + "value": "mlitre" + }, + { + "text": "litre (L)", + "value": "litre" + }, + { + "text": "cubic metre", + "value": "m3" + }, + { + "text": "Normal cubic metre", + "value": "Nm3" + }, + { + "text": "cubic decimetre", + "value": "dm3" + }, + { + "text": "gallons", + "value": "gallons" + } + ], + "text": "volume" + }, + { + "submenu": [ + { + "text": "Watt (W)", + "value": "watt" + }, + { + "text": "Kilowatt (kW)", + "value": "kwatt" + }, + { + "text": "Milliwatt (mW)", + "value": "mwatt" + }, + { + "text": "Watt per square metre (W/m²)", + "value": "Wm2" + }, + { + "text": "Volt-Ampere (VA)", + "value": "voltamp" + }, + { + "text": "Kilovolt-Ampere (kVA)", + "value": "kvoltamp" + }, + { + "text": "Volt-Ampere reactive (VAr)", + "value": "voltampreact" + }, + { + "text": "Kilovolt-Ampere reactive (kVAr)", + "value": "kvoltampreact" + }, + { + "text": "Watt-hour (Wh)", + "value": "watth" + }, + { + "text": "Kilowatt-hour (kWh)", + "value": "kwatth" + }, + { + "text": "Kilowatt-min (kWm)", + "value": "kwattm" + }, + { + "text": "Joule (J)", + "value": "joule" + }, + { + "text": "Electron volt (eV)", + "value": "ev" + }, + { + "text": "Ampere (A)", + "value": "amp" + }, + { + "text": "Kiloampere (kA)", + "value": "kamp" + }, + { + "text": "Milliampere (mA)", + "value": "mamp" + }, + { + "text": "Volt (V)", + "value": "volt" + }, + { + "text": "Kilovolt (kV)", + "value": "kvolt" + }, + { + "text": "Millivolt (mV)", + "value": "mvolt" + }, + { + "text": "Decibel-milliwatt (dBm)", + "value": "dBm" + }, + { + "text": "Ohm (Ω)", + "value": "ohm" + }, + { + "text": "Lumens (Lm)", + "value": "lumens" + } + ], + "text": "energy" + }, + { + "submenu": [ + { + "text": "Celsius (°C)", + "value": "celsius" + }, + { + "text": "Fahrenheit (°F)", + "value": "fahrenheit" + }, + { + "text": "Kelvin (K)", + "value": "kelvin" + } + ], + "text": "temperature" + }, + { + "submenu": [ + { + "text": "Millibars", + "value": "pressurembar" + }, + { + "text": "Bars", + "value": "pressurebar" + }, + { + "text": "Kilobars", + "value": "pressurekbar" + }, + { + "text": "Hectopascals", + "value": "pressurehpa" + }, + { + "text": "Kilopascals", + "value": "pressurekpa" + }, + { + "text": "Inches of mercury", + "value": "pressurehg" + }, + { + "text": "PSI", + "value": "pressurepsi" + } + ], + "text": "pressure" + }, + { + "submenu": [ + { + "text": "Newton-meters (Nm)", + "value": "forceNm" + }, + { + "text": "Kilonewton-meters (kNm)", + "value": "forcekNm" + }, + { + "text": "Newtons (N)", + "value": "forceN" + }, + { + "text": "Kilonewtons (kN)", + "value": "forcekN" + } + ], + "text": "force" + }, + { + "submenu": [ + { + "text": "Gallons/min (gpm)", + "value": "flowgpm" + }, + { + "text": "Cubic meters/sec (cms)", + "value": "flowcms" + }, + { + "text": "Cubic feet/sec (cfs)", + "value": "flowcfs" + }, + { + "text": "Cubic feet/min (cfm)", + "value": "flowcfm" + }, + { + "text": "Litre/hour", + "value": "litreh" + }, + { + "text": "Litre/min (l/min)", + "value": "flowlpm" + }, + { + "text": "milliLitre/min (mL/min)", + "value": "flowmlpm" + } + ], + "text": "flow" + }, + { + "submenu": [ + { + "text": "Degrees (°)", + "value": "degree" + }, + { + "text": "Radians", + "value": "radian" + }, + { + "text": "Gradian", + "value": "grad" + } + ], + "text": "angle" + }, + { + "submenu": [ + { + "text": "Meters/sec²", + "value": "accMS2" + }, + { + "text": "Feet/sec²", + "value": "accFS2" + }, + { + "text": "G unit", + "value": "accG" + } + ], + "text": "acceleration" + }, + { + "submenu": [ + { + "text": "Becquerel (Bq)", + "value": "radbq" + }, + { + "text": "curie (Ci)", + "value": "radci" + }, + { + "text": "Gray (Gy)", + "value": "radgy" + }, + { + "text": "rad", + "value": "radrad" + }, + { + "text": "Sievert (Sv)", + "value": "radsv" + }, + { + "text": "rem", + "value": "radrem" + }, + { + "text": "Exposure (C/kg)", + "value": "radexpckg" + }, + { + "text": "roentgen (R)", + "value": "radr" + }, + { + "text": "Sievert/hour (Sv/h)", + "value": "radsvh" + } + ], + "text": "radiation" + }, + { + "submenu": [ + { + "text": "parts-per-million (ppm)", + "value": "ppm" + }, + { + "text": "parts-per-billion (ppb)", + "value": "conppb" + }, + { + "text": "nanogram per cubic metre (ng/m³)", + "value": "conngm3" + }, + { + "text": "nanogram per normal cubic metre (ng/Nm³)", + "value": "conngNm3" + }, + { + "text": "microgram per cubic metre (μg/m³)", + "value": "conμgm3" + }, + { + "text": "microgram per normal cubic metre (μg/Nm³)", + "value": "conμgNm3" + }, + { + "text": "milligram per cubic metre (mg/m³)", + "value": "conmgm3" + }, + { + "text": "milligram per normal cubic metre (mg/Nm³)", + "value": "conmgNm3" + }, + { + "text": "gram per cubic metre (g/m³)", + "value": "congm3" + }, + { + "text": "gram per normal cubic metre (g/Nm³)", + "value": "congNm3" + } + ], + "text": "concentration" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -337,7 +2525,1090 @@ "kind": "grafana-polystat-panel", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-polystat-panel", + "originalOptions": { + "animationModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "d3DivId": "d3_svg_5", + "decimals": 2, + "displayModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "fontSizes": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, + 48, + 50, + 52, + 54, + 56, + 58, + 60, + 62, + 64, + 66, + 68, + 70 + ], + "fontTypes": [ + "Open Sans", + "Arial", + "Avant Garde", + "Bookman", + "Consolas", + "Courier", + "Courier New", + "Futura", + "Garamond", + "Helvetica", + "Palatino", + "Times", + "Times New Roman", + "Verdana" + ], + "format": "none", + "notcolors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "operatorName": "avg", + "operatorOptions": [ + { + "text": "Average", + "value": "avg" + }, + { + "text": "Count", + "value": "count" + }, + { + "text": "Current", + "value": "current" + }, + { + "text": "Delta", + "value": "delta" + }, + { + "text": "Difference", + "value": "diff" + }, + { + "text": "First", + "value": "first" + }, + { + "text": "Log Min", + "value": "logmin" + }, + { + "text": "Max", + "value": "max" + }, + { + "text": "Min", + "value": "min" + }, + { + "text": "Name", + "value": "name" + }, + { + "text": "Time of Last Point", + "value": "last_time" + }, + { + "text": "Time Step", + "value": "time_step" + }, + { + "text": "Total", + "value": "total" + } + ], + "polystat": { + "animationSpeed": 2500, + "columnAutoSize": true, + "columns": "", + "defaultClickThrough": "", + "defaultClickThroughSanitize": true, + "displayLimit": 100, + "fontAutoScale": true, + "fontSize": 12, + "globalDisplayMode": "all", + "globalOperatorName": "avg", + "gradientEnabled": true, + "hexagonSortByDirection": "asc", + "hexagonSortByField": "name", + "maxMetrics": 0, + "polygonBorderColor": "black", + "polygonBorderSize": 2, + "radius": "", + "radiusAutoSize": true, + "rowAutoSize": true, + "rows": "", + "shape": "hexagon_pointed_top", + "tooltipDisplayMode": "all", + "tooltipDisplayTextTriggeredEmpty": "OK", + "tooltipFontSize": 12, + "tooltipFontType": "Open Sans", + "tooltipPrimarySortDirection": "desc", + "tooltipPrimarySortField": "thresholdLevel", + "tooltipSecondarySortDirection": "desc", + "tooltipSecondarySortField": "value", + "tooltipTimestampEnabled": true + }, + "savedComposites": [ + { + "animateMode": "all", + "clickThrough": "", + "compositeName": "comp", + "enabled": true, + "hideMembers": true, + "members": [ + { + "seriesName": "A-series" + }, + { + "seriesName": "B-series" + } + ], + "sanitizeURLEnabled": true, + "sanitizedURL": "", + "showName": true, + "showValue": true, + "thresholdLevel": 0 + } + ], + "savedOverrides": [], + "shapes": [ + { + "text": "Hexagon Pointed Top", + "value": "hexagon_pointed_top" + }, + { + "text": "Hexagon Flat Top", + "value": "hexagon_flat_top" + }, + { + "text": "Circle", + "value": "circle" + }, + { + "text": "Cross", + "value": "cross" + }, + { + "text": "Diamond", + "value": "diamond" + }, + { + "text": "Square", + "value": "square" + }, + { + "text": "Star", + "value": "star" + }, + { + "text": "Triangle", + "value": "triangle" + }, + { + "text": "Wye", + "value": "wye" + } + ], + "sortDirections": [ + { + "text": "Ascending", + "value": "asc" + }, + { + "text": "Descending", + "value": "desc" + } + ], + "sortFields": [ + { + "text": "Name", + "value": "name" + }, + { + "text": "Threshold Level", + "value": "thresholdLevel" + }, + { + "text": "Value", + "value": "value" + } + ], + "svgContainer": {}, + "thresholdStates": [ + { + "text": "ok", + "value": 0 + }, + { + "text": "warning", + "value": 1 + }, + { + "text": "critical", + "value": 2 + }, + { + "text": "custom", + "value": 3 + } + ], + "unitFormats": [ + { + "submenu": [ + { + "text": "none", + "value": "none" + }, + { + "text": "short", + "value": "short" + }, + { + "text": "percent (0-100)", + "value": "percent" + }, + { + "text": "percent (0.0-1.0)", + "value": "percentunit" + }, + { + "text": "Humidity (%H)", + "value": "humidity" + }, + { + "text": "decibel", + "value": "dB" + }, + { + "text": "hexadecimal (0x)", + "value": "hex0x" + }, + { + "text": "hexadecimal", + "value": "hex" + }, + { + "text": "scientific notation", + "value": "sci" + }, + { + "text": "locale format", + "value": "locale" + } + ], + "text": "none" + }, + { + "submenu": [ + { + "text": "Dollars ($)", + "value": "currencyUSD" + }, + { + "text": "Pounds (£)", + "value": "currencyGBP" + }, + { + "text": "Euro (€)", + "value": "currencyEUR" + }, + { + "text": "Yen (¥)", + "value": "currencyJPY" + }, + { + "text": "Rubles (₽)", + "value": "currencyRUB" + }, + { + "text": "Hryvnias (₴)", + "value": "currencyUAH" + }, + { + "text": "Real (R$)", + "value": "currencyBRL" + }, + { + "text": "Danish Krone (kr)", + "value": "currencyDKK" + }, + { + "text": "Icelandic Króna (kr)", + "value": "currencyISK" + }, + { + "text": "Norwegian Krone (kr)", + "value": "currencyNOK" + }, + { + "text": "Swedish Krona (kr)", + "value": "currencySEK" + }, + { + "text": "Czech koruna (czk)", + "value": "currencyCZK" + }, + { + "text": "Swiss franc (CHF)", + "value": "currencyCHF" + }, + { + "text": "Polish Złoty (PLN)", + "value": "currencyPLN" + }, + { + "text": "Bitcoin (฿)", + "value": "currencyBTC" + }, + { + "text": "Vietnamese Dong (VND)", + "value": "currencyVND" + }, + { + "text": "Malaysian Ringgit (RM)", + "value": "currencyMYR" + }, + { + "text": "Bulgarian Lev (BGN)", + "value": "currencyBGN" + } + ], + "text": "currency" + }, + { + "submenu": [ + { + "text": "Hertz (1/s)", + "value": "hertz" + }, + { + "text": "nanoseconds (ns)", + "value": "ns" + }, + { + "text": "microseconds (µs)", + "value": "µs" + }, + { + "text": "milliseconds (ms)", + "value": "ms" + }, + { + "text": "seconds (s)", + "value": "s" + }, + { + "text": "minutes (m)", + "value": "m" + }, + { + "text": "hours (h)", + "value": "h" + }, + { + "text": "days (d)", + "value": "d" + }, + { + "text": "duration (ms)", + "value": "dtdurationms" + }, + { + "text": "duration (s)", + "value": "dtdurations" + }, + { + "text": "duration (hh:mm:ss)", + "value": "dthms" + }, + { + "text": "Timeticks (s/100)", + "value": "timeticks" + } + ], + "text": "time" + }, + { + "submenu": [ + { + "text": "YYYY-MM-DD HH:mm:ss", + "value": "dateTimeAsIso" + }, + { + "text": "DD/MM/YYYY h:mm:ss a", + "value": "dateTimeAsUS" + }, + { + "text": "From Now", + "value": "dateTimeFromNow" + } + ], + "text": "date \u0026 time" + }, + { + "submenu": [ + { + "text": "bits", + "value": "bits" + }, + { + "text": "bytes", + "value": "bytes" + }, + { + "text": "kibibytes", + "value": "kbytes" + }, + { + "text": "mebibytes", + "value": "mbytes" + }, + { + "text": "gibibytes", + "value": "gbytes" + } + ], + "text": "data (IEC)" + }, + { + "submenu": [ + { + "text": "bits", + "value": "decbits" + }, + { + "text": "bytes", + "value": "decbytes" + }, + { + "text": "kilobytes", + "value": "deckbytes" + }, + { + "text": "megabytes", + "value": "decmbytes" + }, + { + "text": "gigabytes", + "value": "decgbytes" + } + ], + "text": "data (Metric)" + }, + { + "submenu": [ + { + "text": "packets/sec", + "value": "pps" + }, + { + "text": "bits/sec", + "value": "bps" + }, + { + "text": "bytes/sec", + "value": "Bps" + }, + { + "text": "kilobits/sec", + "value": "Kbits" + }, + { + "text": "kilobytes/sec", + "value": "KBs" + }, + { + "text": "megabits/sec", + "value": "Mbits" + }, + { + "text": "megabytes/sec", + "value": "MBs" + }, + { + "text": "gigabytes/sec", + "value": "GBs" + }, + { + "text": "gigabits/sec", + "value": "Gbits" + } + ], + "text": "data rate" + }, + { + "submenu": [ + { + "text": "hashes/sec", + "value": "Hs" + }, + { + "text": "kilohashes/sec", + "value": "KHs" + }, + { + "text": "megahashes/sec", + "value": "MHs" + }, + { + "text": "gigahashes/sec", + "value": "GHs" + }, + { + "text": "terahashes/sec", + "value": "THs" + }, + { + "text": "petahashes/sec", + "value": "PHs" + }, + { + "text": "exahashes/sec", + "value": "EHs" + } + ], + "text": "hash rate" + }, + { + "submenu": [ + { + "text": "ops/sec (ops)", + "value": "ops" + }, + { + "text": "requests/sec (rps)", + "value": "reqps" + }, + { + "text": "reads/sec (rps)", + "value": "rps" + }, + { + "text": "writes/sec (wps)", + "value": "wps" + }, + { + "text": "I/O ops/sec (iops)", + "value": "iops" + }, + { + "text": "ops/min (opm)", + "value": "opm" + }, + { + "text": "reads/min (rpm)", + "value": "rpm" + }, + { + "text": "writes/min (wpm)", + "value": "wpm" + } + ], + "text": "throughput" + }, + { + "submenu": [ + { + "text": "millimetre (mm)", + "value": "lengthmm" + }, + { + "text": "meter (m)", + "value": "lengthm" + }, + { + "text": "inch (in)", + "value": "lengthin" + }, + { + "text": "feet (ft)", + "value": "lengthft" + }, + { + "text": "kilometer (km)", + "value": "lengthkm" + }, + { + "text": "mile (mi)", + "value": "lengthmi" + } + ], + "text": "length" + }, + { + "submenu": [ + { + "text": "Square Meters (m²)", + "value": "areaM2" + }, + { + "text": "Square Feet (ft²)", + "value": "areaF2" + }, + { + "text": "Square Miles (mi²)", + "value": "areaMI2" + } + ], + "text": "area" + }, + { + "submenu": [ + { + "text": "milligram (mg)", + "value": "massmg" + }, + { + "text": "gram (g)", + "value": "massg" + }, + { + "text": "pound (lb)", + "value": "masslb" + }, + { + "text": "kilogram (kg)", + "value": "masskg" + }, + { + "text": "metric ton (t)", + "value": "masst" + } + ], + "text": "mass" + }, + { + "submenu": [ + { + "text": "metres/second (m/s)", + "value": "velocityms" + }, + { + "text": "kilometers/hour (km/h)", + "value": "velocitykmh" + }, + { + "text": "miles/hour (mph)", + "value": "velocitymph" + }, + { + "text": "knot (kn)", + "value": "velocityknot" + } + ], + "text": "velocity" + }, + { + "submenu": [ + { + "text": "millilitre (mL)", + "value": "mlitre" + }, + { + "text": "litre (L)", + "value": "litre" + }, + { + "text": "cubic metre", + "value": "m3" + }, + { + "text": "Normal cubic metre", + "value": "Nm3" + }, + { + "text": "cubic decimetre", + "value": "dm3" + }, + { + "text": "gallons", + "value": "gallons" + } + ], + "text": "volume" + }, + { + "submenu": [ + { + "text": "Watt (W)", + "value": "watt" + }, + { + "text": "Kilowatt (kW)", + "value": "kwatt" + }, + { + "text": "Milliwatt (mW)", + "value": "mwatt" + }, + { + "text": "Watt per square metre (W/m²)", + "value": "Wm2" + }, + { + "text": "Volt-ampere (VA)", + "value": "voltamp" + }, + { + "text": "Kilovolt-ampere (kVA)", + "value": "kvoltamp" + }, + { + "text": "Volt-ampere reactive (var)", + "value": "voltampreact" + }, + { + "text": "Kilovolt-ampere reactive (kvar)", + "value": "kvoltampreact" + }, + { + "text": "Watt-hour (Wh)", + "value": "watth" + }, + { + "text": "Kilowatt-hour (kWh)", + "value": "kwatth" + }, + { + "text": "Kilowatt-min (kWm)", + "value": "kwattm" + }, + { + "text": "Joule (J)", + "value": "joule" + }, + { + "text": "Electron volt (eV)", + "value": "ev" + }, + { + "text": "Ampere (A)", + "value": "amp" + }, + { + "text": "Kiloampere (kA)", + "value": "kamp" + }, + { + "text": "Milliampere (mA)", + "value": "mamp" + }, + { + "text": "Volt (V)", + "value": "volt" + }, + { + "text": "Kilovolt (kV)", + "value": "kvolt" + }, + { + "text": "Millivolt (mV)", + "value": "mvolt" + }, + { + "text": "Decibel-milliwatt (dBm)", + "value": "dBm" + }, + { + "text": "Ohm (Ω)", + "value": "ohm" + }, + { + "text": "Lumens (Lm)", + "value": "lumens" + } + ], + "text": "energy" + }, + { + "submenu": [ + { + "text": "Celsius (°C)", + "value": "celsius" + }, + { + "text": "Fahrenheit (°F)", + "value": "fahrenheit" + }, + { + "text": "Kelvin (K)", + "value": "kelvin" + } + ], + "text": "temperature" + }, + { + "submenu": [ + { + "text": "Millibars", + "value": "pressurembar" + }, + { + "text": "Bars", + "value": "pressurebar" + }, + { + "text": "Kilobars", + "value": "pressurekbar" + }, + { + "text": "Hectopascals", + "value": "pressurehpa" + }, + { + "text": "Kilopascals", + "value": "pressurekpa" + }, + { + "text": "Inches of mercury", + "value": "pressurehg" + }, + { + "text": "PSI", + "value": "pressurepsi" + } + ], + "text": "pressure" + }, + { + "submenu": [ + { + "text": "Newton-meters (Nm)", + "value": "forceNm" + }, + { + "text": "Kilonewton-meters (kNm)", + "value": "forcekNm" + }, + { + "text": "Newtons (N)", + "value": "forceN" + }, + { + "text": "Kilonewtons (kN)", + "value": "forcekN" + } + ], + "text": "force" + }, + { + "submenu": [ + { + "text": "Gallons/min (gpm)", + "value": "flowgpm" + }, + { + "text": "Cubic meters/sec (cms)", + "value": "flowcms" + }, + { + "text": "Cubic feet/sec (cfs)", + "value": "flowcfs" + }, + { + "text": "Cubic feet/min (cfm)", + "value": "flowcfm" + }, + { + "text": "Litre/hour", + "value": "litreh" + }, + { + "text": "Litre/min (l/min)", + "value": "flowlpm" + }, + { + "text": "milliLitre/min (mL/min)", + "value": "flowmlpm" + } + ], + "text": "flow" + }, + { + "submenu": [ + { + "text": "Degrees (°)", + "value": "degree" + }, + { + "text": "Radians", + "value": "radian" + }, + { + "text": "Gradian", + "value": "grad" + } + ], + "text": "angle" + }, + { + "submenu": [ + { + "text": "Meters/sec²", + "value": "accMS2" + }, + { + "text": "Feet/sec²", + "value": "accFS2" + }, + { + "text": "G unit", + "value": "accG" + } + ], + "text": "acceleration" + }, + { + "submenu": [ + { + "text": "Becquerel (Bq)", + "value": "radbq" + }, + { + "text": "curie (Ci)", + "value": "radci" + }, + { + "text": "Gray (Gy)", + "value": "radgy" + }, + { + "text": "rad", + "value": "radrad" + }, + { + "text": "Sievert (Sv)", + "value": "radsv" + }, + { + "text": "rem", + "value": "radrem" + }, + { + "text": "Exposure (C/kg)", + "value": "radexpckg" + }, + { + "text": "roentgen (R)", + "value": "radr" + }, + { + "text": "Sievert/hour (Sv/h)", + "value": "radsvh" + } + ], + "text": "radiation" + }, + { + "submenu": [ + { + "text": "parts-per-million (ppm)", + "value": "ppm" + }, + { + "text": "parts-per-billion (ppb)", + "value": "conppb" + }, + { + "text": "nanogram per cubic metre (ng/m³)", + "value": "conngm3" + }, + { + "text": "nanogram per normal cubic metre (ng/Nm³)", + "value": "conngNm3" + }, + { + "text": "microgram per cubic metre (μg/m³)", + "value": "conμgm3" + }, + { + "text": "microgram per normal cubic metre (μg/Nm³)", + "value": "conμgNm3" + }, + { + "text": "milligram per cubic metre (mg/m³)", + "value": "conmgm3" + }, + { + "text": "milligram per normal cubic metre (mg/Nm³)", + "value": "conmgNm3" + }, + { + "text": "gram per cubic metre (g/m³)", + "value": "congm3" + }, + { + "text": "gram per normal cubic metre (g/Nm³)", + "value": "congNm3" + } + ], + "text": "concentration" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2beta1.json index f72f1e3b485..5916250ffd5 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-polystat/v0alpha1.polystat_test.v42.v2beta1.json @@ -130,7 +130,1073 @@ "group": "grafana-polystat-panel", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-polystat-panel", + "originalOptions": { + "animationModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "d3DivId": "d3_svg_2", + "decimals": 2, + "displayModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "fontSizes": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, + 48, + 50, + 52, + 54, + 56, + 58, + 60, + 62, + 64, + 66, + 68, + 70 + ], + "fontTypes": [ + "Open Sans", + "Arial", + "Avant Garde", + "Bookman", + "Consolas", + "Courier", + "Courier New", + "Futura", + "Garamond", + "Helvetica", + "Palatino", + "Times", + "Times New Roman", + "Verdana" + ], + "format": "none", + "notcolors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "operatorName": "avg", + "operatorOptions": [ + { + "text": "Average", + "value": "avg" + }, + { + "text": "Count", + "value": "count" + }, + { + "text": "Current", + "value": "current" + }, + { + "text": "Delta", + "value": "delta" + }, + { + "text": "Difference", + "value": "diff" + }, + { + "text": "First", + "value": "first" + }, + { + "text": "Log Min", + "value": "logmin" + }, + { + "text": "Max", + "value": "max" + }, + { + "text": "Min", + "value": "min" + }, + { + "text": "Name", + "value": "name" + }, + { + "text": "Time of Last Point", + "value": "last_time" + }, + { + "text": "Time Step", + "value": "time_step" + }, + { + "text": "Total", + "value": "total" + } + ], + "polystat": { + "animationSpeed": 2500, + "columnAutoSize": true, + "columns": 1, + "defaultClickThrough": "", + "defaultClickThroughSanitize": true, + "displayLimit": 100, + "fontAutoScale": true, + "fontSize": 12, + "globalDisplayMode": "all", + "globalOperatorName": "avg", + "gradientEnabled": true, + "hexagonSortByDirection": "asc", + "hexagonSortByField": "name", + "maxMetrics": 0, + "polygonBorderColor": "black", + "polygonBorderSize": 2, + "radius": "", + "radiusAutoSize": true, + "rowAutoSize": true, + "rows": 1, + "shape": "hexagon_pointed_top", + "tooltipDisplayMode": "all", + "tooltipDisplayTextTriggeredEmpty": "OK", + "tooltipFontSize": 12, + "tooltipFontType": "Open Sans", + "tooltipPrimarySortDirection": "desc", + "tooltipPrimarySortField": "thresholdLevel", + "tooltipSecondarySortDirection": "desc", + "tooltipSecondarySortField": "value", + "tooltipTimestampEnabled": true + }, + "savedComposites": [], + "savedOverrides": [], + "shapes": [ + { + "text": "Hexagon Pointed Top", + "value": "hexagon_pointed_top" + }, + { + "text": "Hexagon Flat Top", + "value": "hexagon_flat_top" + }, + { + "text": "Circle", + "value": "circle" + }, + { + "text": "Cross", + "value": "cross" + }, + { + "text": "Diamond", + "value": "diamond" + }, + { + "text": "Square", + "value": "square" + }, + { + "text": "Star", + "value": "star" + }, + { + "text": "Triangle", + "value": "triangle" + }, + { + "text": "Wye", + "value": "wye" + } + ], + "sortDirections": [ + { + "text": "Ascending", + "value": "asc" + }, + { + "text": "Descending", + "value": "desc" + } + ], + "sortFields": [ + { + "text": "Name", + "value": "name" + }, + { + "text": "Threshold Level", + "value": "thresholdLevel" + }, + { + "text": "Value", + "value": "value" + } + ], + "svgContainer": {}, + "thresholdStates": [ + { + "text": "ok", + "value": 0 + }, + { + "text": "warning", + "value": 1 + }, + { + "text": "critical", + "value": 2 + }, + { + "text": "custom", + "value": 3 + } + ], + "unitFormats": [ + { + "submenu": [ + { + "text": "none", + "value": "none" + }, + { + "text": "short", + "value": "short" + }, + { + "text": "percent (0-100)", + "value": "percent" + }, + { + "text": "percent (0.0-1.0)", + "value": "percentunit" + }, + { + "text": "Humidity (%H)", + "value": "humidity" + }, + { + "text": "decibel", + "value": "dB" + }, + { + "text": "hexadecimal (0x)", + "value": "hex0x" + }, + { + "text": "hexadecimal", + "value": "hex" + }, + { + "text": "scientific notation", + "value": "sci" + }, + { + "text": "locale format", + "value": "locale" + } + ], + "text": "none" + }, + { + "submenu": [ + { + "text": "Dollars ($)", + "value": "currencyUSD" + }, + { + "text": "Pounds (£)", + "value": "currencyGBP" + }, + { + "text": "Euro (€)", + "value": "currencyEUR" + }, + { + "text": "Yen (¥)", + "value": "currencyJPY" + }, + { + "text": "Rubles (₽)", + "value": "currencyRUB" + }, + { + "text": "Hryvnias (₴)", + "value": "currencyUAH" + }, + { + "text": "Real (R$)", + "value": "currencyBRL" + }, + { + "text": "Danish Krone (kr)", + "value": "currencyDKK" + }, + { + "text": "Icelandic Króna (kr)", + "value": "currencyISK" + }, + { + "text": "Norwegian Krone (kr)", + "value": "currencyNOK" + }, + { + "text": "Swedish Krona (kr)", + "value": "currencySEK" + }, + { + "text": "Czech koruna (czk)", + "value": "currencyCZK" + }, + { + "text": "Swiss franc (CHF)", + "value": "currencyCHF" + }, + { + "text": "Polish Złoty (PLN)", + "value": "currencyPLN" + }, + { + "text": "Bitcoin (฿)", + "value": "currencyBTC" + }, + { + "text": "Vietnamese Dong (VND)", + "value": "currencyVND" + }, + { + "text": "Malaysian Ringgit (RM)", + "value": "currencyMYR" + }, + { + "text": "Bulgarian Lev (BGN)", + "value": "currencyBGN" + } + ], + "text": "currency" + }, + { + "submenu": [ + { + "text": "Hertz (1/s)", + "value": "hertz" + }, + { + "text": "nanoseconds (ns)", + "value": "ns" + }, + { + "text": "microseconds (µs)", + "value": "µs" + }, + { + "text": "milliseconds (ms)", + "value": "ms" + }, + { + "text": "seconds (s)", + "value": "s" + }, + { + "text": "minutes (m)", + "value": "m" + }, + { + "text": "hours (h)", + "value": "h" + }, + { + "text": "days (d)", + "value": "d" + }, + { + "text": "duration (ms)", + "value": "dtdurationms" + }, + { + "text": "duration (s)", + "value": "dtdurations" + }, + { + "text": "duration (hh:mm:ss)", + "value": "dthms" + }, + { + "text": "Timeticks (s/100)", + "value": "timeticks" + } + ], + "text": "time" + }, + { + "submenu": [ + { + "text": "YYYY-MM-DD HH:mm:ss", + "value": "dateTimeAsIso" + }, + { + "text": "DD/MM/YYYY h:mm:ss a", + "value": "dateTimeAsUS" + }, + { + "text": "From Now", + "value": "dateTimeFromNow" + } + ], + "text": "date \u0026 time" + }, + { + "submenu": [ + { + "text": "bits", + "value": "bits" + }, + { + "text": "bytes", + "value": "bytes" + }, + { + "text": "kibibytes", + "value": "kbytes" + }, + { + "text": "mebibytes", + "value": "mbytes" + }, + { + "text": "gibibytes", + "value": "gbytes" + } + ], + "text": "data (IEC)" + }, + { + "submenu": [ + { + "text": "bits", + "value": "decbits" + }, + { + "text": "bytes", + "value": "decbytes" + }, + { + "text": "kilobytes", + "value": "deckbytes" + }, + { + "text": "megabytes", + "value": "decmbytes" + }, + { + "text": "gigabytes", + "value": "decgbytes" + } + ], + "text": "data (Metric)" + }, + { + "submenu": [ + { + "text": "packets/sec", + "value": "pps" + }, + { + "text": "bits/sec", + "value": "bps" + }, + { + "text": "bytes/sec", + "value": "Bps" + }, + { + "text": "kilobits/sec", + "value": "Kbits" + }, + { + "text": "kilobytes/sec", + "value": "KBs" + }, + { + "text": "megabits/sec", + "value": "Mbits" + }, + { + "text": "megabytes/sec", + "value": "MBs" + }, + { + "text": "gigabytes/sec", + "value": "GBs" + }, + { + "text": "gigabits/sec", + "value": "Gbits" + } + ], + "text": "data rate" + }, + { + "submenu": [ + { + "text": "hashes/sec", + "value": "Hs" + }, + { + "text": "kilohashes/sec", + "value": "KHs" + }, + { + "text": "megahashes/sec", + "value": "MHs" + }, + { + "text": "gigahashes/sec", + "value": "GHs" + }, + { + "text": "terahashes/sec", + "value": "THs" + }, + { + "text": "petahashes/sec", + "value": "PHs" + }, + { + "text": "exahashes/sec", + "value": "EHs" + } + ], + "text": "hash rate" + }, + { + "submenu": [ + { + "text": "ops/sec (ops)", + "value": "ops" + }, + { + "text": "requests/sec (rps)", + "value": "reqps" + }, + { + "text": "reads/sec (rps)", + "value": "rps" + }, + { + "text": "writes/sec (wps)", + "value": "wps" + }, + { + "text": "I/O ops/sec (iops)", + "value": "iops" + }, + { + "text": "ops/min (opm)", + "value": "opm" + }, + { + "text": "requests/min (rpm)", + "value": "reqpm" + }, + { + "text": "reads/min (rpm)", + "value": "rpm" + }, + { + "text": "writes/min (wpm)", + "value": "wpm" + } + ], + "text": "throughput" + }, + { + "submenu": [ + { + "text": "millimetre (mm)", + "value": "lengthmm" + }, + { + "text": "meter (m)", + "value": "lengthm" + }, + { + "text": "inch (in)", + "value": "lengthin" + }, + { + "text": "feet (ft)", + "value": "lengthft" + }, + { + "text": "kilometer (km)", + "value": "lengthkm" + }, + { + "text": "mile (mi)", + "value": "lengthmi" + } + ], + "text": "length" + }, + { + "submenu": [ + { + "text": "Square Meters (m²)", + "value": "areaM2" + }, + { + "text": "Square Feet (ft²)", + "value": "areaF2" + }, + { + "text": "Square Miles (mi²)", + "value": "areaMI2" + } + ], + "text": "area" + }, + { + "submenu": [ + { + "text": "milligram (mg)", + "value": "massmg" + }, + { + "text": "gram (g)", + "value": "massg" + }, + { + "text": "pound (lb)", + "value": "masslb" + }, + { + "text": "kilogram (kg)", + "value": "masskg" + }, + { + "text": "metric ton (t)", + "value": "masst" + } + ], + "text": "mass" + }, + { + "submenu": [ + { + "text": "metres/second (m/s)", + "value": "velocityms" + }, + { + "text": "kilometers/hour (km/h)", + "value": "velocitykmh" + }, + { + "text": "miles/hour (mph)", + "value": "velocitymph" + }, + { + "text": "knot (kn)", + "value": "velocityknot" + } + ], + "text": "velocity" + }, + { + "submenu": [ + { + "text": "millilitre (mL)", + "value": "mlitre" + }, + { + "text": "litre (L)", + "value": "litre" + }, + { + "text": "cubic metre", + "value": "m3" + }, + { + "text": "Normal cubic metre", + "value": "Nm3" + }, + { + "text": "cubic decimetre", + "value": "dm3" + }, + { + "text": "gallons", + "value": "gallons" + } + ], + "text": "volume" + }, + { + "submenu": [ + { + "text": "Watt (W)", + "value": "watt" + }, + { + "text": "Kilowatt (kW)", + "value": "kwatt" + }, + { + "text": "Milliwatt (mW)", + "value": "mwatt" + }, + { + "text": "Watt per square metre (W/m²)", + "value": "Wm2" + }, + { + "text": "Volt-ampere (VA)", + "value": "voltamp" + }, + { + "text": "Kilovolt-ampere (kVA)", + "value": "kvoltamp" + }, + { + "text": "Volt-ampere reactive (var)", + "value": "voltampreact" + }, + { + "text": "Kilovolt-ampere reactive (kvar)", + "value": "kvoltampreact" + }, + { + "text": "Watt-hour (Wh)", + "value": "watth" + }, + { + "text": "Kilowatt-hour (kWh)", + "value": "kwatth" + }, + { + "text": "Kilowatt-min (kWm)", + "value": "kwattm" + }, + { + "text": "Joule (J)", + "value": "joule" + }, + { + "text": "Electron volt (eV)", + "value": "ev" + }, + { + "text": "Ampere (A)", + "value": "amp" + }, + { + "text": "Kiloampere (kA)", + "value": "kamp" + }, + { + "text": "Milliampere (mA)", + "value": "mamp" + }, + { + "text": "Volt (V)", + "value": "volt" + }, + { + "text": "Kilovolt (kV)", + "value": "kvolt" + }, + { + "text": "Millivolt (mV)", + "value": "mvolt" + }, + { + "text": "Decibel-milliwatt (dBm)", + "value": "dBm" + }, + { + "text": "Ohm (Ω)", + "value": "ohm" + }, + { + "text": "Lumens (Lm)", + "value": "lumens" + } + ], + "text": "energy" + }, + { + "submenu": [ + { + "text": "Celsius (°C)", + "value": "celsius" + }, + { + "text": "Fahrenheit (°F)", + "value": "fahrenheit" + }, + { + "text": "Kelvin (K)", + "value": "kelvin" + } + ], + "text": "temperature" + }, + { + "submenu": [ + { + "text": "Millibars", + "value": "pressurembar" + }, + { + "text": "Bars", + "value": "pressurebar" + }, + { + "text": "Kilobars", + "value": "pressurekbar" + }, + { + "text": "Hectopascals", + "value": "pressurehpa" + }, + { + "text": "Kilopascals", + "value": "pressurekpa" + }, + { + "text": "Inches of mercury", + "value": "pressurehg" + }, + { + "text": "PSI", + "value": "pressurepsi" + } + ], + "text": "pressure" + }, + { + "submenu": [ + { + "text": "Newton-meters (Nm)", + "value": "forceNm" + }, + { + "text": "Kilonewton-meters (kNm)", + "value": "forcekNm" + }, + { + "text": "Newtons (N)", + "value": "forceN" + }, + { + "text": "Kilonewtons (kN)", + "value": "forcekN" + } + ], + "text": "force" + }, + { + "submenu": [ + { + "text": "Gallons/min (gpm)", + "value": "flowgpm" + }, + { + "text": "Cubic meters/sec (cms)", + "value": "flowcms" + }, + { + "text": "Cubic feet/sec (cfs)", + "value": "flowcfs" + }, + { + "text": "Cubic feet/min (cfm)", + "value": "flowcfm" + }, + { + "text": "Litre/hour", + "value": "litreh" + }, + { + "text": "Litre/min (l/min)", + "value": "flowlpm" + }, + { + "text": "milliLitre/min (mL/min)", + "value": "flowmlpm" + } + ], + "text": "flow" + }, + { + "submenu": [ + { + "text": "Degrees (°)", + "value": "degree" + }, + { + "text": "Radians", + "value": "radian" + }, + { + "text": "Gradian", + "value": "grad" + } + ], + "text": "angle" + }, + { + "submenu": [ + { + "text": "Meters/sec²", + "value": "accMS2" + }, + { + "text": "Feet/sec²", + "value": "accFS2" + }, + { + "text": "G unit", + "value": "accG" + } + ], + "text": "acceleration" + }, + { + "submenu": [ + { + "text": "Becquerel (Bq)", + "value": "radbq" + }, + { + "text": "curie (Ci)", + "value": "radci" + }, + { + "text": "Gray (Gy)", + "value": "radgy" + }, + { + "text": "rad", + "value": "radrad" + }, + { + "text": "Sievert (Sv)", + "value": "radsv" + }, + { + "text": "rem", + "value": "radrem" + }, + { + "text": "Exposure (C/kg)", + "value": "radexpckg" + }, + { + "text": "roentgen (R)", + "value": "radr" + }, + { + "text": "Sievert/hour (Sv/h)", + "value": "radsvh" + } + ], + "text": "radiation" + }, + { + "submenu": [ + { + "text": "parts-per-million (ppm)", + "value": "ppm" + }, + { + "text": "parts-per-billion (ppb)", + "value": "conppb" + }, + { + "text": "nanogram per cubic metre (ng/m³)", + "value": "conngm3" + }, + { + "text": "nanogram per normal cubic metre (ng/Nm³)", + "value": "conngNm3" + }, + { + "text": "microgram per cubic metre (μg/m³)", + "value": "conμgm3" + }, + { + "text": "microgram per normal cubic metre (μg/Nm³)", + "value": "conμgNm3" + }, + { + "text": "milligram per cubic metre (mg/m³)", + "value": "conmgm3" + }, + { + "text": "milligram per normal cubic metre (mg/Nm³)", + "value": "conmgNm3" + }, + { + "text": "gram per cubic metre (g/m³)", + "value": "congm3" + }, + { + "text": "gram per normal cubic metre (g/Nm³)", + "value": "congNm3" + } + ], + "text": "concentration" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -250,7 +1316,1129 @@ "group": "grafana-polystat-panel", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-polystat-panel", + "originalOptions": { + "animationModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "d3DivId": "d3_svg_4", + "decimals": 2, + "displayModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "fontSizes": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, + 48, + 50, + 52, + 54, + 56, + 58, + 60, + 62, + 64, + 66, + 68, + 70 + ], + "fontTypes": [ + "Open Sans", + "Arial", + "Avant Garde", + "Bookman", + "Consolas", + "Courier", + "Courier New", + "Futura", + "Garamond", + "Helvetica", + "Palatino", + "Times", + "Times New Roman", + "Verdana" + ], + "format": "none", + "notcolors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "operatorName": "avg", + "operatorOptions": [ + { + "text": "Average", + "value": "avg" + }, + { + "text": "Count", + "value": "count" + }, + { + "text": "Current", + "value": "current" + }, + { + "text": "Delta", + "value": "delta" + }, + { + "text": "Difference", + "value": "diff" + }, + { + "text": "First", + "value": "first" + }, + { + "text": "Log Min", + "value": "logmin" + }, + { + "text": "Max", + "value": "max" + }, + { + "text": "Min", + "value": "min" + }, + { + "text": "Name", + "value": "name" + }, + { + "text": "Time of Last Point", + "value": "last_time" + }, + { + "text": "Time Step", + "value": "time_step" + }, + { + "text": "Total", + "value": "total" + } + ], + "polystat": { + "animationSpeed": 2500, + "columnAutoSize": true, + "columns": "", + "defaultClickThrough": "", + "defaultClickThroughSanitize": true, + "displayLimit": 100, + "fontAutoScale": true, + "fontSize": 12, + "globalDisplayMode": "all", + "globalOperatorName": "avg", + "gradientEnabled": true, + "hexagonSortByDirection": "asc", + "hexagonSortByField": "name", + "maxMetrics": 0, + "polygonBorderColor": "black", + "polygonBorderSize": 2, + "radius": "", + "radiusAutoSize": true, + "rowAutoSize": true, + "rows": "", + "shape": "hexagon_pointed_top", + "tooltipDisplayMode": "all", + "tooltipDisplayTextTriggeredEmpty": "OK", + "tooltipFontSize": 12, + "tooltipFontType": "Open Sans", + "tooltipPrimarySortDirection": "desc", + "tooltipPrimarySortField": "thresholdLevel", + "tooltipSecondarySortDirection": "desc", + "tooltipSecondarySortField": "value", + "tooltipTimestampEnabled": true + }, + "savedComposites": [], + "savedOverrides": [], + "shapes": [ + { + "text": "Hexagon Pointed Top", + "value": "hexagon_pointed_top" + }, + { + "text": "Hexagon Flat Top", + "value": "hexagon_flat_top" + }, + { + "text": "Circle", + "value": "circle" + }, + { + "text": "Cross", + "value": "cross" + }, + { + "text": "Diamond", + "value": "diamond" + }, + { + "text": "Square", + "value": "square" + }, + { + "text": "Star", + "value": "star" + }, + { + "text": "Triangle", + "value": "triangle" + }, + { + "text": "Wye", + "value": "wye" + } + ], + "sortDirections": [ + { + "text": "Ascending", + "value": "asc" + }, + { + "text": "Descending", + "value": "desc" + } + ], + "sortFields": [ + { + "text": "Name", + "value": "name" + }, + { + "text": "Threshold Level", + "value": "thresholdLevel" + }, + { + "text": "Value", + "value": "value" + } + ], + "svgContainer": {}, + "thresholdStates": [ + { + "text": "ok", + "value": 0 + }, + { + "text": "warning", + "value": 1 + }, + { + "text": "critical", + "value": 2 + }, + { + "text": "custom", + "value": 3 + } + ], + "unitFormats": [ + { + "submenu": [ + { + "text": "none", + "value": "none" + }, + { + "text": "short", + "value": "short" + }, + { + "text": "percent (0-100)", + "value": "percent" + }, + { + "text": "percent (0.0-1.0)", + "value": "percentunit" + }, + { + "text": "Humidity (%H)", + "value": "humidity" + }, + { + "text": "decibel", + "value": "dB" + }, + { + "text": "hexadecimal (0x)", + "value": "hex0x" + }, + { + "text": "hexadecimal", + "value": "hex" + }, + { + "text": "scientific notation", + "value": "sci" + }, + { + "text": "locale format", + "value": "locale" + } + ], + "text": "none" + }, + { + "submenu": [ + { + "text": "Dollars ($)", + "value": "currencyUSD" + }, + { + "text": "Pounds (£)", + "value": "currencyGBP" + }, + { + "text": "Euro (€)", + "value": "currencyEUR" + }, + { + "text": "Yen (¥)", + "value": "currencyJPY" + }, + { + "text": "Rubles (₽)", + "value": "currencyRUB" + }, + { + "text": "Hryvnias (₴)", + "value": "currencyUAH" + }, + { + "text": "Real (R$)", + "value": "currencyBRL" + }, + { + "text": "Danish Krone (kr)", + "value": "currencyDKK" + }, + { + "text": "Icelandic Króna (kr)", + "value": "currencyISK" + }, + { + "text": "Norwegian Krone (kr)", + "value": "currencyNOK" + }, + { + "text": "Swedish Krona (kr)", + "value": "currencySEK" + }, + { + "text": "Czech koruna (czk)", + "value": "currencyCZK" + }, + { + "text": "Swiss franc (CHF)", + "value": "currencyCHF" + }, + { + "text": "Polish Złoty (PLN)", + "value": "currencyPLN" + }, + { + "text": "Bitcoin (฿)", + "value": "currencyBTC" + }, + { + "text": "Milli Bitcoin (mBTC)", + "value": "currencymBTC" + }, + { + "text": "Micro Bitcoin (μBTC)", + "value": "currencyμBTC" + }, + { + "text": "Vietnamese Dong (VND)", + "value": "currencyVND" + }, + { + "text": "Turkish Lira (₺)", + "value": "currencyTRY" + }, + { + "text": "Malaysian Ringgit (RM)", + "value": "currencyMYR" + }, + { + "text": "CFP franc (XPF)", + "value": "currencyXPF" + }, + { + "text": "Bulgarian Lev (BGN)", + "value": "currencyBGN" + } + ], + "text": "currency" + }, + { + "submenu": [ + { + "text": "Hertz (1/s)", + "value": "hertz" + }, + { + "text": "nanoseconds (ns)", + "value": "ns" + }, + { + "text": "microseconds (µs)", + "value": "µs" + }, + { + "text": "milliseconds (ms)", + "value": "ms" + }, + { + "text": "seconds (s)", + "value": "s" + }, + { + "text": "minutes (m)", + "value": "m" + }, + { + "text": "hours (h)", + "value": "h" + }, + { + "text": "days (d)", + "value": "d" + }, + { + "text": "duration (ms)", + "value": "dtdurationms" + }, + { + "text": "duration (s)", + "value": "dtdurations" + }, + { + "text": "duration (hh:mm:ss)", + "value": "dthms" + }, + { + "text": "Timeticks (s/100)", + "value": "timeticks" + } + ], + "text": "time" + }, + { + "submenu": [ + { + "text": "YYYY-MM-DD HH:mm:ss", + "value": "dateTimeAsIso" + }, + { + "text": "DD/MM/YYYY h:mm:ss a", + "value": "dateTimeAsUS" + }, + { + "text": "From Now", + "value": "dateTimeFromNow" + } + ], + "text": "date \u0026 time" + }, + { + "submenu": [ + { + "text": "bits", + "value": "bits" + }, + { + "text": "bytes", + "value": "bytes" + }, + { + "text": "kibibytes", + "value": "kbytes" + }, + { + "text": "mebibytes", + "value": "mbytes" + }, + { + "text": "gibibytes", + "value": "gbytes" + } + ], + "text": "data (IEC)" + }, + { + "submenu": [ + { + "text": "bits", + "value": "decbits" + }, + { + "text": "bytes", + "value": "decbytes" + }, + { + "text": "kilobytes", + "value": "deckbytes" + }, + { + "text": "megabytes", + "value": "decmbytes" + }, + { + "text": "gigabytes", + "value": "decgbytes" + } + ], + "text": "data (Metric)" + }, + { + "submenu": [ + { + "text": "packets/sec", + "value": "pps" + }, + { + "text": "bits/sec", + "value": "bps" + }, + { + "text": "bytes/sec", + "value": "Bps" + }, + { + "text": "kilobits/sec", + "value": "Kbits" + }, + { + "text": "kilobytes/sec", + "value": "KBs" + }, + { + "text": "megabits/sec", + "value": "Mbits" + }, + { + "text": "megabytes/sec", + "value": "MBs" + }, + { + "text": "gigabytes/sec", + "value": "GBs" + }, + { + "text": "gigabits/sec", + "value": "Gbits" + } + ], + "text": "data rate" + }, + { + "submenu": [ + { + "text": "hashes/sec", + "value": "Hs" + }, + { + "text": "kilohashes/sec", + "value": "KHs" + }, + { + "text": "megahashes/sec", + "value": "MHs" + }, + { + "text": "gigahashes/sec", + "value": "GHs" + }, + { + "text": "terahashes/sec", + "value": "THs" + }, + { + "text": "petahashes/sec", + "value": "PHs" + }, + { + "text": "exahashes/sec", + "value": "EHs" + } + ], + "text": "hash rate" + }, + { + "submenu": [ + { + "text": "counts/sec (cps)", + "value": "cps" + }, + { + "text": "ops/sec (ops)", + "value": "ops" + }, + { + "text": "requests/sec (rps)", + "value": "reqps" + }, + { + "text": "reads/sec (rps)", + "value": "rps" + }, + { + "text": "writes/sec (wps)", + "value": "wps" + }, + { + "text": "I/O ops/sec (iops)", + "value": "iops" + }, + { + "text": "events/sec (eps)", + "value": "eps" + }, + { + "text": "messages/sec (mps)", + "value": "mps" + }, + { + "text": "records/sec (rps)", + "value": "recps" + }, + { + "text": "rows/sec (rps)", + "value": "rowsps" + }, + { + "text": "counts/min (cpm)", + "value": "cpm" + }, + { + "text": "ops/min (opm)", + "value": "opm" + }, + { + "text": "requests/min (rpm)", + "value": "reqpm" + }, + { + "text": "reads/min (rpm)", + "value": "rpm" + }, + { + "text": "writes/min (wpm)", + "value": "wpm" + }, + { + "text": "events/min (epm)", + "value": "epm" + }, + { + "text": "messages/min (mpm)", + "value": "mpm" + }, + { + "text": "records/min (rpm)", + "value": "recpm" + }, + { + "text": "rows/min (rpm)", + "value": "rowspm" + } + ], + "text": "throughput" + }, + { + "submenu": [ + { + "text": "millimetre (mm)", + "value": "lengthmm" + }, + { + "text": "meter (m)", + "value": "lengthm" + }, + { + "text": "inch (in)", + "value": "lengthin" + }, + { + "text": "feet (ft)", + "value": "lengthft" + }, + { + "text": "kilometer (km)", + "value": "lengthkm" + }, + { + "text": "mile (mi)", + "value": "lengthmi" + } + ], + "text": "length" + }, + { + "submenu": [ + { + "text": "Square Meters (m²)", + "value": "areaM2" + }, + { + "text": "Square Feet (ft²)", + "value": "areaF2" + }, + { + "text": "Square Miles (mi²)", + "value": "areaMI2" + } + ], + "text": "area" + }, + { + "submenu": [ + { + "text": "milligram (mg)", + "value": "massmg" + }, + { + "text": "gram (g)", + "value": "massg" + }, + { + "text": "pound (lb)", + "value": "masslb" + }, + { + "text": "kilogram (kg)", + "value": "masskg" + }, + { + "text": "metric ton (t)", + "value": "masst" + } + ], + "text": "mass" + }, + { + "submenu": [ + { + "text": "metres/second (m/s)", + "value": "velocityms" + }, + { + "text": "kilometers/hour (km/h)", + "value": "velocitykmh" + }, + { + "text": "miles/hour (mph)", + "value": "velocitymph" + }, + { + "text": "knot (kn)", + "value": "velocityknot" + } + ], + "text": "velocity" + }, + { + "submenu": [ + { + "text": "millilitre (mL)", + "value": "mlitre" + }, + { + "text": "litre (L)", + "value": "litre" + }, + { + "text": "cubic metre", + "value": "m3" + }, + { + "text": "Normal cubic metre", + "value": "Nm3" + }, + { + "text": "cubic decimetre", + "value": "dm3" + }, + { + "text": "gallons", + "value": "gallons" + } + ], + "text": "volume" + }, + { + "submenu": [ + { + "text": "Watt (W)", + "value": "watt" + }, + { + "text": "Kilowatt (kW)", + "value": "kwatt" + }, + { + "text": "Milliwatt (mW)", + "value": "mwatt" + }, + { + "text": "Watt per square metre (W/m²)", + "value": "Wm2" + }, + { + "text": "Volt-Ampere (VA)", + "value": "voltamp" + }, + { + "text": "Kilovolt-Ampere (kVA)", + "value": "kvoltamp" + }, + { + "text": "Volt-Ampere reactive (VAr)", + "value": "voltampreact" + }, + { + "text": "Kilovolt-Ampere reactive (kVAr)", + "value": "kvoltampreact" + }, + { + "text": "Watt-hour (Wh)", + "value": "watth" + }, + { + "text": "Kilowatt-hour (kWh)", + "value": "kwatth" + }, + { + "text": "Kilowatt-min (kWm)", + "value": "kwattm" + }, + { + "text": "Joule (J)", + "value": "joule" + }, + { + "text": "Electron volt (eV)", + "value": "ev" + }, + { + "text": "Ampere (A)", + "value": "amp" + }, + { + "text": "Kiloampere (kA)", + "value": "kamp" + }, + { + "text": "Milliampere (mA)", + "value": "mamp" + }, + { + "text": "Volt (V)", + "value": "volt" + }, + { + "text": "Kilovolt (kV)", + "value": "kvolt" + }, + { + "text": "Millivolt (mV)", + "value": "mvolt" + }, + { + "text": "Decibel-milliwatt (dBm)", + "value": "dBm" + }, + { + "text": "Ohm (Ω)", + "value": "ohm" + }, + { + "text": "Lumens (Lm)", + "value": "lumens" + } + ], + "text": "energy" + }, + { + "submenu": [ + { + "text": "Celsius (°C)", + "value": "celsius" + }, + { + "text": "Fahrenheit (°F)", + "value": "fahrenheit" + }, + { + "text": "Kelvin (K)", + "value": "kelvin" + } + ], + "text": "temperature" + }, + { + "submenu": [ + { + "text": "Millibars", + "value": "pressurembar" + }, + { + "text": "Bars", + "value": "pressurebar" + }, + { + "text": "Kilobars", + "value": "pressurekbar" + }, + { + "text": "Hectopascals", + "value": "pressurehpa" + }, + { + "text": "Kilopascals", + "value": "pressurekpa" + }, + { + "text": "Inches of mercury", + "value": "pressurehg" + }, + { + "text": "PSI", + "value": "pressurepsi" + } + ], + "text": "pressure" + }, + { + "submenu": [ + { + "text": "Newton-meters (Nm)", + "value": "forceNm" + }, + { + "text": "Kilonewton-meters (kNm)", + "value": "forcekNm" + }, + { + "text": "Newtons (N)", + "value": "forceN" + }, + { + "text": "Kilonewtons (kN)", + "value": "forcekN" + } + ], + "text": "force" + }, + { + "submenu": [ + { + "text": "Gallons/min (gpm)", + "value": "flowgpm" + }, + { + "text": "Cubic meters/sec (cms)", + "value": "flowcms" + }, + { + "text": "Cubic feet/sec (cfs)", + "value": "flowcfs" + }, + { + "text": "Cubic feet/min (cfm)", + "value": "flowcfm" + }, + { + "text": "Litre/hour", + "value": "litreh" + }, + { + "text": "Litre/min (l/min)", + "value": "flowlpm" + }, + { + "text": "milliLitre/min (mL/min)", + "value": "flowmlpm" + } + ], + "text": "flow" + }, + { + "submenu": [ + { + "text": "Degrees (°)", + "value": "degree" + }, + { + "text": "Radians", + "value": "radian" + }, + { + "text": "Gradian", + "value": "grad" + } + ], + "text": "angle" + }, + { + "submenu": [ + { + "text": "Meters/sec²", + "value": "accMS2" + }, + { + "text": "Feet/sec²", + "value": "accFS2" + }, + { + "text": "G unit", + "value": "accG" + } + ], + "text": "acceleration" + }, + { + "submenu": [ + { + "text": "Becquerel (Bq)", + "value": "radbq" + }, + { + "text": "curie (Ci)", + "value": "radci" + }, + { + "text": "Gray (Gy)", + "value": "radgy" + }, + { + "text": "rad", + "value": "radrad" + }, + { + "text": "Sievert (Sv)", + "value": "radsv" + }, + { + "text": "rem", + "value": "radrem" + }, + { + "text": "Exposure (C/kg)", + "value": "radexpckg" + }, + { + "text": "roentgen (R)", + "value": "radr" + }, + { + "text": "Sievert/hour (Sv/h)", + "value": "radsvh" + } + ], + "text": "radiation" + }, + { + "submenu": [ + { + "text": "parts-per-million (ppm)", + "value": "ppm" + }, + { + "text": "parts-per-billion (ppb)", + "value": "conppb" + }, + { + "text": "nanogram per cubic metre (ng/m³)", + "value": "conngm3" + }, + { + "text": "nanogram per normal cubic metre (ng/Nm³)", + "value": "conngNm3" + }, + { + "text": "microgram per cubic metre (μg/m³)", + "value": "conμgm3" + }, + { + "text": "microgram per normal cubic metre (μg/Nm³)", + "value": "conμgNm3" + }, + { + "text": "milligram per cubic metre (mg/m³)", + "value": "conmgm3" + }, + { + "text": "milligram per normal cubic metre (mg/Nm³)", + "value": "conmgNm3" + }, + { + "text": "gram per cubic metre (g/m³)", + "value": "congm3" + }, + { + "text": "gram per normal cubic metre (g/Nm³)", + "value": "congNm3" + } + ], + "text": "concentration" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] @@ -370,7 +2558,1090 @@ "group": "grafana-polystat-panel", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "grafana-polystat-panel", + "originalOptions": { + "animationModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "d3DivId": "d3_svg_5", + "decimals": 2, + "displayModes": [ + { + "text": "Show All", + "value": "all" + }, + { + "text": "Show Triggered", + "value": "triggered" + } + ], + "fontSizes": [ + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, + 48, + 50, + 52, + 54, + 56, + 58, + 60, + 62, + 64, + 66, + 68, + 70 + ], + "fontTypes": [ + "Open Sans", + "Arial", + "Avant Garde", + "Bookman", + "Consolas", + "Courier", + "Courier New", + "Futura", + "Garamond", + "Helvetica", + "Palatino", + "Times", + "Times New Roman", + "Verdana" + ], + "format": "none", + "notcolors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "operatorName": "avg", + "operatorOptions": [ + { + "text": "Average", + "value": "avg" + }, + { + "text": "Count", + "value": "count" + }, + { + "text": "Current", + "value": "current" + }, + { + "text": "Delta", + "value": "delta" + }, + { + "text": "Difference", + "value": "diff" + }, + { + "text": "First", + "value": "first" + }, + { + "text": "Log Min", + "value": "logmin" + }, + { + "text": "Max", + "value": "max" + }, + { + "text": "Min", + "value": "min" + }, + { + "text": "Name", + "value": "name" + }, + { + "text": "Time of Last Point", + "value": "last_time" + }, + { + "text": "Time Step", + "value": "time_step" + }, + { + "text": "Total", + "value": "total" + } + ], + "polystat": { + "animationSpeed": 2500, + "columnAutoSize": true, + "columns": "", + "defaultClickThrough": "", + "defaultClickThroughSanitize": true, + "displayLimit": 100, + "fontAutoScale": true, + "fontSize": 12, + "globalDisplayMode": "all", + "globalOperatorName": "avg", + "gradientEnabled": true, + "hexagonSortByDirection": "asc", + "hexagonSortByField": "name", + "maxMetrics": 0, + "polygonBorderColor": "black", + "polygonBorderSize": 2, + "radius": "", + "radiusAutoSize": true, + "rowAutoSize": true, + "rows": "", + "shape": "hexagon_pointed_top", + "tooltipDisplayMode": "all", + "tooltipDisplayTextTriggeredEmpty": "OK", + "tooltipFontSize": 12, + "tooltipFontType": "Open Sans", + "tooltipPrimarySortDirection": "desc", + "tooltipPrimarySortField": "thresholdLevel", + "tooltipSecondarySortDirection": "desc", + "tooltipSecondarySortField": "value", + "tooltipTimestampEnabled": true + }, + "savedComposites": [ + { + "animateMode": "all", + "clickThrough": "", + "compositeName": "comp", + "enabled": true, + "hideMembers": true, + "members": [ + { + "seriesName": "A-series" + }, + { + "seriesName": "B-series" + } + ], + "sanitizeURLEnabled": true, + "sanitizedURL": "", + "showName": true, + "showValue": true, + "thresholdLevel": 0 + } + ], + "savedOverrides": [], + "shapes": [ + { + "text": "Hexagon Pointed Top", + "value": "hexagon_pointed_top" + }, + { + "text": "Hexagon Flat Top", + "value": "hexagon_flat_top" + }, + { + "text": "Circle", + "value": "circle" + }, + { + "text": "Cross", + "value": "cross" + }, + { + "text": "Diamond", + "value": "diamond" + }, + { + "text": "Square", + "value": "square" + }, + { + "text": "Star", + "value": "star" + }, + { + "text": "Triangle", + "value": "triangle" + }, + { + "text": "Wye", + "value": "wye" + } + ], + "sortDirections": [ + { + "text": "Ascending", + "value": "asc" + }, + { + "text": "Descending", + "value": "desc" + } + ], + "sortFields": [ + { + "text": "Name", + "value": "name" + }, + { + "text": "Threshold Level", + "value": "thresholdLevel" + }, + { + "text": "Value", + "value": "value" + } + ], + "svgContainer": {}, + "thresholdStates": [ + { + "text": "ok", + "value": 0 + }, + { + "text": "warning", + "value": 1 + }, + { + "text": "critical", + "value": 2 + }, + { + "text": "custom", + "value": 3 + } + ], + "unitFormats": [ + { + "submenu": [ + { + "text": "none", + "value": "none" + }, + { + "text": "short", + "value": "short" + }, + { + "text": "percent (0-100)", + "value": "percent" + }, + { + "text": "percent (0.0-1.0)", + "value": "percentunit" + }, + { + "text": "Humidity (%H)", + "value": "humidity" + }, + { + "text": "decibel", + "value": "dB" + }, + { + "text": "hexadecimal (0x)", + "value": "hex0x" + }, + { + "text": "hexadecimal", + "value": "hex" + }, + { + "text": "scientific notation", + "value": "sci" + }, + { + "text": "locale format", + "value": "locale" + } + ], + "text": "none" + }, + { + "submenu": [ + { + "text": "Dollars ($)", + "value": "currencyUSD" + }, + { + "text": "Pounds (£)", + "value": "currencyGBP" + }, + { + "text": "Euro (€)", + "value": "currencyEUR" + }, + { + "text": "Yen (¥)", + "value": "currencyJPY" + }, + { + "text": "Rubles (₽)", + "value": "currencyRUB" + }, + { + "text": "Hryvnias (₴)", + "value": "currencyUAH" + }, + { + "text": "Real (R$)", + "value": "currencyBRL" + }, + { + "text": "Danish Krone (kr)", + "value": "currencyDKK" + }, + { + "text": "Icelandic Króna (kr)", + "value": "currencyISK" + }, + { + "text": "Norwegian Krone (kr)", + "value": "currencyNOK" + }, + { + "text": "Swedish Krona (kr)", + "value": "currencySEK" + }, + { + "text": "Czech koruna (czk)", + "value": "currencyCZK" + }, + { + "text": "Swiss franc (CHF)", + "value": "currencyCHF" + }, + { + "text": "Polish Złoty (PLN)", + "value": "currencyPLN" + }, + { + "text": "Bitcoin (฿)", + "value": "currencyBTC" + }, + { + "text": "Vietnamese Dong (VND)", + "value": "currencyVND" + }, + { + "text": "Malaysian Ringgit (RM)", + "value": "currencyMYR" + }, + { + "text": "Bulgarian Lev (BGN)", + "value": "currencyBGN" + } + ], + "text": "currency" + }, + { + "submenu": [ + { + "text": "Hertz (1/s)", + "value": "hertz" + }, + { + "text": "nanoseconds (ns)", + "value": "ns" + }, + { + "text": "microseconds (µs)", + "value": "µs" + }, + { + "text": "milliseconds (ms)", + "value": "ms" + }, + { + "text": "seconds (s)", + "value": "s" + }, + { + "text": "minutes (m)", + "value": "m" + }, + { + "text": "hours (h)", + "value": "h" + }, + { + "text": "days (d)", + "value": "d" + }, + { + "text": "duration (ms)", + "value": "dtdurationms" + }, + { + "text": "duration (s)", + "value": "dtdurations" + }, + { + "text": "duration (hh:mm:ss)", + "value": "dthms" + }, + { + "text": "Timeticks (s/100)", + "value": "timeticks" + } + ], + "text": "time" + }, + { + "submenu": [ + { + "text": "YYYY-MM-DD HH:mm:ss", + "value": "dateTimeAsIso" + }, + { + "text": "DD/MM/YYYY h:mm:ss a", + "value": "dateTimeAsUS" + }, + { + "text": "From Now", + "value": "dateTimeFromNow" + } + ], + "text": "date \u0026 time" + }, + { + "submenu": [ + { + "text": "bits", + "value": "bits" + }, + { + "text": "bytes", + "value": "bytes" + }, + { + "text": "kibibytes", + "value": "kbytes" + }, + { + "text": "mebibytes", + "value": "mbytes" + }, + { + "text": "gibibytes", + "value": "gbytes" + } + ], + "text": "data (IEC)" + }, + { + "submenu": [ + { + "text": "bits", + "value": "decbits" + }, + { + "text": "bytes", + "value": "decbytes" + }, + { + "text": "kilobytes", + "value": "deckbytes" + }, + { + "text": "megabytes", + "value": "decmbytes" + }, + { + "text": "gigabytes", + "value": "decgbytes" + } + ], + "text": "data (Metric)" + }, + { + "submenu": [ + { + "text": "packets/sec", + "value": "pps" + }, + { + "text": "bits/sec", + "value": "bps" + }, + { + "text": "bytes/sec", + "value": "Bps" + }, + { + "text": "kilobits/sec", + "value": "Kbits" + }, + { + "text": "kilobytes/sec", + "value": "KBs" + }, + { + "text": "megabits/sec", + "value": "Mbits" + }, + { + "text": "megabytes/sec", + "value": "MBs" + }, + { + "text": "gigabytes/sec", + "value": "GBs" + }, + { + "text": "gigabits/sec", + "value": "Gbits" + } + ], + "text": "data rate" + }, + { + "submenu": [ + { + "text": "hashes/sec", + "value": "Hs" + }, + { + "text": "kilohashes/sec", + "value": "KHs" + }, + { + "text": "megahashes/sec", + "value": "MHs" + }, + { + "text": "gigahashes/sec", + "value": "GHs" + }, + { + "text": "terahashes/sec", + "value": "THs" + }, + { + "text": "petahashes/sec", + "value": "PHs" + }, + { + "text": "exahashes/sec", + "value": "EHs" + } + ], + "text": "hash rate" + }, + { + "submenu": [ + { + "text": "ops/sec (ops)", + "value": "ops" + }, + { + "text": "requests/sec (rps)", + "value": "reqps" + }, + { + "text": "reads/sec (rps)", + "value": "rps" + }, + { + "text": "writes/sec (wps)", + "value": "wps" + }, + { + "text": "I/O ops/sec (iops)", + "value": "iops" + }, + { + "text": "ops/min (opm)", + "value": "opm" + }, + { + "text": "reads/min (rpm)", + "value": "rpm" + }, + { + "text": "writes/min (wpm)", + "value": "wpm" + } + ], + "text": "throughput" + }, + { + "submenu": [ + { + "text": "millimetre (mm)", + "value": "lengthmm" + }, + { + "text": "meter (m)", + "value": "lengthm" + }, + { + "text": "inch (in)", + "value": "lengthin" + }, + { + "text": "feet (ft)", + "value": "lengthft" + }, + { + "text": "kilometer (km)", + "value": "lengthkm" + }, + { + "text": "mile (mi)", + "value": "lengthmi" + } + ], + "text": "length" + }, + { + "submenu": [ + { + "text": "Square Meters (m²)", + "value": "areaM2" + }, + { + "text": "Square Feet (ft²)", + "value": "areaF2" + }, + { + "text": "Square Miles (mi²)", + "value": "areaMI2" + } + ], + "text": "area" + }, + { + "submenu": [ + { + "text": "milligram (mg)", + "value": "massmg" + }, + { + "text": "gram (g)", + "value": "massg" + }, + { + "text": "pound (lb)", + "value": "masslb" + }, + { + "text": "kilogram (kg)", + "value": "masskg" + }, + { + "text": "metric ton (t)", + "value": "masst" + } + ], + "text": "mass" + }, + { + "submenu": [ + { + "text": "metres/second (m/s)", + "value": "velocityms" + }, + { + "text": "kilometers/hour (km/h)", + "value": "velocitykmh" + }, + { + "text": "miles/hour (mph)", + "value": "velocitymph" + }, + { + "text": "knot (kn)", + "value": "velocityknot" + } + ], + "text": "velocity" + }, + { + "submenu": [ + { + "text": "millilitre (mL)", + "value": "mlitre" + }, + { + "text": "litre (L)", + "value": "litre" + }, + { + "text": "cubic metre", + "value": "m3" + }, + { + "text": "Normal cubic metre", + "value": "Nm3" + }, + { + "text": "cubic decimetre", + "value": "dm3" + }, + { + "text": "gallons", + "value": "gallons" + } + ], + "text": "volume" + }, + { + "submenu": [ + { + "text": "Watt (W)", + "value": "watt" + }, + { + "text": "Kilowatt (kW)", + "value": "kwatt" + }, + { + "text": "Milliwatt (mW)", + "value": "mwatt" + }, + { + "text": "Watt per square metre (W/m²)", + "value": "Wm2" + }, + { + "text": "Volt-ampere (VA)", + "value": "voltamp" + }, + { + "text": "Kilovolt-ampere (kVA)", + "value": "kvoltamp" + }, + { + "text": "Volt-ampere reactive (var)", + "value": "voltampreact" + }, + { + "text": "Kilovolt-ampere reactive (kvar)", + "value": "kvoltampreact" + }, + { + "text": "Watt-hour (Wh)", + "value": "watth" + }, + { + "text": "Kilowatt-hour (kWh)", + "value": "kwatth" + }, + { + "text": "Kilowatt-min (kWm)", + "value": "kwattm" + }, + { + "text": "Joule (J)", + "value": "joule" + }, + { + "text": "Electron volt (eV)", + "value": "ev" + }, + { + "text": "Ampere (A)", + "value": "amp" + }, + { + "text": "Kiloampere (kA)", + "value": "kamp" + }, + { + "text": "Milliampere (mA)", + "value": "mamp" + }, + { + "text": "Volt (V)", + "value": "volt" + }, + { + "text": "Kilovolt (kV)", + "value": "kvolt" + }, + { + "text": "Millivolt (mV)", + "value": "mvolt" + }, + { + "text": "Decibel-milliwatt (dBm)", + "value": "dBm" + }, + { + "text": "Ohm (Ω)", + "value": "ohm" + }, + { + "text": "Lumens (Lm)", + "value": "lumens" + } + ], + "text": "energy" + }, + { + "submenu": [ + { + "text": "Celsius (°C)", + "value": "celsius" + }, + { + "text": "Fahrenheit (°F)", + "value": "fahrenheit" + }, + { + "text": "Kelvin (K)", + "value": "kelvin" + } + ], + "text": "temperature" + }, + { + "submenu": [ + { + "text": "Millibars", + "value": "pressurembar" + }, + { + "text": "Bars", + "value": "pressurebar" + }, + { + "text": "Kilobars", + "value": "pressurekbar" + }, + { + "text": "Hectopascals", + "value": "pressurehpa" + }, + { + "text": "Kilopascals", + "value": "pressurekpa" + }, + { + "text": "Inches of mercury", + "value": "pressurehg" + }, + { + "text": "PSI", + "value": "pressurepsi" + } + ], + "text": "pressure" + }, + { + "submenu": [ + { + "text": "Newton-meters (Nm)", + "value": "forceNm" + }, + { + "text": "Kilonewton-meters (kNm)", + "value": "forcekNm" + }, + { + "text": "Newtons (N)", + "value": "forceN" + }, + { + "text": "Kilonewtons (kN)", + "value": "forcekN" + } + ], + "text": "force" + }, + { + "submenu": [ + { + "text": "Gallons/min (gpm)", + "value": "flowgpm" + }, + { + "text": "Cubic meters/sec (cms)", + "value": "flowcms" + }, + { + "text": "Cubic feet/sec (cfs)", + "value": "flowcfs" + }, + { + "text": "Cubic feet/min (cfm)", + "value": "flowcfm" + }, + { + "text": "Litre/hour", + "value": "litreh" + }, + { + "text": "Litre/min (l/min)", + "value": "flowlpm" + }, + { + "text": "milliLitre/min (mL/min)", + "value": "flowmlpm" + } + ], + "text": "flow" + }, + { + "submenu": [ + { + "text": "Degrees (°)", + "value": "degree" + }, + { + "text": "Radians", + "value": "radian" + }, + { + "text": "Gradian", + "value": "grad" + } + ], + "text": "angle" + }, + { + "submenu": [ + { + "text": "Meters/sec²", + "value": "accMS2" + }, + { + "text": "Feet/sec²", + "value": "accFS2" + }, + { + "text": "G unit", + "value": "accG" + } + ], + "text": "acceleration" + }, + { + "submenu": [ + { + "text": "Becquerel (Bq)", + "value": "radbq" + }, + { + "text": "curie (Ci)", + "value": "radci" + }, + { + "text": "Gray (Gy)", + "value": "radgy" + }, + { + "text": "rad", + "value": "radrad" + }, + { + "text": "Sievert (Sv)", + "value": "radsv" + }, + { + "text": "rem", + "value": "radrem" + }, + { + "text": "Exposure (C/kg)", + "value": "radexpckg" + }, + { + "text": "roentgen (R)", + "value": "radr" + }, + { + "text": "Sievert/hour (Sv/h)", + "value": "radsvh" + } + ], + "text": "radiation" + }, + { + "submenu": [ + { + "text": "parts-per-million (ppm)", + "value": "ppm" + }, + { + "text": "parts-per-billion (ppb)", + "value": "conppb" + }, + { + "text": "nanogram per cubic metre (ng/m³)", + "value": "conngm3" + }, + { + "text": "nanogram per normal cubic metre (ng/Nm³)", + "value": "conngNm3" + }, + { + "text": "microgram per cubic metre (μg/m³)", + "value": "conμgm3" + }, + { + "text": "microgram per normal cubic metre (μg/Nm³)", + "value": "conμgNm3" + }, + { + "text": "milligram per cubic metre (mg/m³)", + "value": "conmgm3" + }, + { + "text": "milligram per normal cubic metre (mg/Nm³)", + "value": "conmgNm3" + }, + { + "text": "gram per cubic metre (g/m³)", + "value": "congm3" + }, + { + "text": "gram per normal cubic metre (g/Nm³)", + "value": "congNm3" + } + ], + "text": "concentration" + } + ] + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2alpha1.json index 315164a75a3..43ca604b064 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2alpha1.json @@ -665,7 +665,42 @@ "kind": "heatmap", "spec": { "pluginVersion": "", - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": false + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": false + }, + "xAxis": { + "show": true + }, + "yAxis": { + "format": "short", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2beta1.json index 94f898bfa16..ae693615b73 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.all-panels.v42.v2beta1.json @@ -691,7 +691,42 @@ "group": "heatmap", "version": "", "spec": { - "options": {}, + "options": { + "__angularMigration": { + "autoMigrateFrom": "heatmap", + "originalOptions": { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "legend": { + "show": false + }, + "reverseYBuckets": false, + "tooltip": { + "show": true, + "showHistogram": false + }, + "xAxis": { + "show": true + }, + "yAxis": { + "format": "short", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + } + } + }, "fieldConfig": { "defaults": {}, "overrides": [] diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2alpha1.json index 2a6500f86db..7c77141f891 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2alpha1.json @@ -56,6 +56,14 @@ "spec": { "pluginVersion": "9.0.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "panel-tests" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -94,6 +102,15 @@ "spec": { "pluginVersion": "9.0.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "gdev", + "demo" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -133,6 +150,15 @@ "spec": { "pluginVersion": "9.0.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "templating", + "gdev" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -172,6 +198,15 @@ "spec": { "pluginVersion": "9.0.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "gdev", + "datasource-test" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -211,6 +246,12 @@ "spec": { "pluginVersion": "9.0.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [] + } + }, "maxItems": 100, "query": "", "showHeadings": true, @@ -247,6 +288,15 @@ "spec": { "pluginVersion": "9.0.0-pre", "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "gdev", + "demo" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2beta1.json index 0580a517cb2..5cec6a9741b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/v0alpha1.v1beta1.v1beta1.home.v42.v2beta1.json @@ -58,6 +58,14 @@ "version": "9.0.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "panel-tests" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -97,6 +105,15 @@ "version": "9.0.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "gdev", + "demo" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -137,6 +154,15 @@ "version": "9.0.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "templating", + "gdev" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -177,6 +203,15 @@ "version": "9.0.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "gdev", + "datasource-test" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, @@ -217,6 +252,12 @@ "version": "9.0.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [] + } + }, "maxItems": 100, "query": "", "showHeadings": true, @@ -254,6 +295,15 @@ "version": "9.0.0-pre", "spec": { "options": { + "__angularMigration": { + "autoMigrateFrom": "dashlist", + "originalOptions": { + "tags": [ + "gdev", + "demo" + ] + } + }, "maxItems": 1000, "query": "", "showHeadings": false, diff --git a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go index db86f8c87a9..47c12a6ac94 100644 --- a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go +++ b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go @@ -2296,20 +2296,24 @@ func buildVizConfig(panelMap map[string]interface{}) dashv2alpha1.DashboardVizCo // We check two cases: // 1. Panel already has autoMigrateFrom set (from v0→v1 migration) - panel type already converted // 2. Panel type is a known Angular panel - need to convert type AND set autoMigrateFrom + // 3. Panel has original options - need to set autoMigrateFrom and originalOptions autoMigrateFrom, hasAutoMigrateFrom := panelMap["autoMigrateFrom"].(string) + originalOptions := extractAngularOptions(panelMap) if !hasAutoMigrateFrom || autoMigrateFrom == "" { // Check if panel type is an Angular type that needs migration if newType := getAngularPanelMigration(panelType, panelMap); newType != "" { autoMigrateFrom = panelType // Original Angular type panelType = newType // New modern type + } else if len(originalOptions) > 0 { + autoMigrateFrom = panelType } } if autoMigrateFrom != "" { options["__angularMigration"] = map[string]interface{}{ "autoMigrateFrom": autoMigrateFrom, - "originalOptions": extractAngularOptions(panelMap), + "originalOptions": originalOptions, } } diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/migrations/migrations.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/migrations/migrations.v42.json index 89ce80876d2..6ff361f144e 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/migrations/migrations.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/migrations/migrations.v42.json @@ -290,6 +290,7 @@ } }, { + "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", "datasource": { "type": "grafana-testdata-datasource" }, @@ -300,15 +301,7 @@ "y": 0 }, "id": 6, - "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "# Graph panel \u003e\u003e Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", - "mode": "markdown" - }, + "mode": "markdown", "pluginVersion": "11.0.0-pre", "targets": [ { diff --git a/devenv/dev-dashboards/migrations/migrations.json b/devenv/dev-dashboards/migrations/migrations.json index 4c2160e1f26..ad063b99cf4 100644 --- a/devenv/dev-dashboards/migrations/migrations.json +++ b/devenv/dev-dashboards/migrations/migrations.json @@ -299,15 +299,9 @@ "y": 0 }, "id": 6, - "options": { - "code": { - "language": "plaintext", - "showLineNumbers": false, - "showMiniMap": false - }, - "content": "# Graph panel >> Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", - "mode": "markdown" - }, + "options": {}, + "content": "# Graph panel >> Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions", + "mode": "markdown", "pluginVersion": "11.0.0-pre", "targets": [ { diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 08f37b316d6..80d18ce0cae 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1888,11 +1888,6 @@ "count": 1 } }, - "public/app/features/dashboard-scene/serialization/angularMigration.test.ts": { - "@typescript-eslint/no-explicit-any": { - "count": 1 - } - }, "public/app/features/dashboard-scene/serialization/buildNewDashboardSaveModel.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 1 diff --git a/public/app/features/dashboard-scene/serialization/angularMigration.test.ts b/public/app/features/dashboard-scene/serialization/angularMigration.test.ts index 46c67f6cd9f..2eea13b90d5 100644 --- a/public/app/features/dashboard-scene/serialization/angularMigration.test.ts +++ b/public/app/features/dashboard-scene/serialization/angularMigration.test.ts @@ -1,9 +1,22 @@ -import { PanelTypeChangedHandler } from '@grafana/data'; +import { FieldConfigSource, PanelTypeChangedHandler } from '@grafana/data'; import { getPanelPlugin } from '@grafana/data/test'; import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { getV2AngularMigrationHandler, getAngularPanelMigrationHandler } from './angularMigration'; +/** + * Test type for mutable panel model used in migration handler tests. + * Allows arbitrary properties to be added during migration. + */ +interface TestPanelModel { + id: number; + type: string; + options: Record; + fieldConfig: FieldConfigSource; + targets?: Array<{ refId: string; [key: string]: unknown }>; + [key: string]: unknown; +} + describe('getAngularPanelMigrationHandler', () => { describe('Given an old angular panel', () => { it('Should call migration handler', () => { @@ -43,9 +56,11 @@ describe('getAngularPanelMigrationHandler', () => { type: 'dashlist', }); - const mutatedModel: any = { + const mutatedModel: TestPanelModel = { + id: 1, type: 'dashlist', options: {}, + fieldConfig: { defaults: {}, overrides: [] }, }; getAngularPanelMigrationHandler(oldModel)(mutatedModel, reactPlugin); @@ -80,7 +95,7 @@ describe('getV2AngularMigrationHandler', () => { }, }; - const mutatedModel = { + const mutatedModel: TestPanelModel = { id: 1, type: 'stat', options: {}, @@ -89,6 +104,11 @@ describe('getV2AngularMigrationHandler', () => { getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + // Verify originalOptions were spread onto the panel (for plugins using setMigrationHandler) + expect(mutatedModel['format']).toBe('short'); + expect(mutatedModel['valueName']).toBe('avg'); + expect(mutatedModel['orientation']).toBe('horizontal'); + // Verify handler received correct arguments expect(receivedPrevPluginId).toBe('singlestat'); expect(receivedPrevOptions?.angular).toBeDefined(); @@ -123,7 +143,7 @@ describe('getV2AngularMigrationHandler', () => { }, }; - const mutatedModel = { + const mutatedModel: TestPanelModel = { id: 1, type: 'timeseries', options: {}, @@ -132,6 +152,10 @@ describe('getV2AngularMigrationHandler', () => { getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + // Verify originalOptions were spread onto the panel (for plugins using setMigrationHandler) + expect(mutatedModel['bars']).toBe(true); + expect(mutatedModel['lines']).toBe(false); + // Verify handler received correct arguments expect(receivedPrevPluginId).toBe('graph'); expect(receivedPrevOptions?.angular).toBeDefined(); @@ -163,7 +187,7 @@ describe('getV2AngularMigrationHandler', () => { }, }; - const mutatedModel = { + const mutatedModel: TestPanelModel = { id: 1, type: 'new-panel', options: {}, @@ -172,6 +196,9 @@ describe('getV2AngularMigrationHandler', () => { getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + // Verify originalOptions were spread onto the panel (for plugins using setMigrationHandler) + expect(mutatedModel['oldOption']).toBe('old'); + // Verify handler received correct arguments (options wrapper, not angular) expect(receivedPrevPluginId).toBe('some-react-panel'); expect(receivedPrevOptions?.options).toBeDefined(); @@ -222,4 +249,118 @@ describe('getV2AngularMigrationHandler', () => { warnSpy.mockRestore(); }); }); + + describe('Given v2 migration data for text panel with Angular-style properties', () => { + it('Should spread originalOptions onto panel for migration handlers using setMigrationHandler', () => { + // Text panel uses setMigrationHandler, not onPanelTypeChanged + // The migration handler expects content/mode to be directly on the panel object + const reactPlugin = getPanelPlugin({ id: 'text' }); + + // This simulates a text panel with Angular-style properties at root level + // The backend sets autoMigrateFrom="text" when it detects these properties + const migrationData = { + autoMigrateFrom: 'text', + originalOptions: { + content: 'Hello World', + mode: 'markdown', + }, + }; + + const mutatedModel: TestPanelModel = { + id: 1, + type: 'text', + options: {}, + fieldConfig: { defaults: {}, overrides: [] }, + }; + + getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + + // Verify originalOptions were spread onto the panel + // This allows textPanelMigrationHandler to see content/mode via panel.hasOwnProperty() + expect(mutatedModel['content']).toBe('Hello World'); + expect(mutatedModel['mode']).toBe('markdown'); + }); + + it('Should not overwrite existing panel properties when spreading originalOptions', () => { + const reactPlugin = getPanelPlugin({ id: 'text' }); + + const migrationData = { + autoMigrateFrom: 'text', + originalOptions: { + content: 'Old content', + mode: 'markdown', + id: 999, // Should not overwrite existing id + }, + }; + + const mutatedModel: TestPanelModel = { + id: 1, + type: 'text', + options: { existingOption: true }, + fieldConfig: { defaults: {}, overrides: [] }, + }; + + getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + + // defaults() only sets properties that don't already exist + expect(mutatedModel['content']).toBe('Old content'); + expect(mutatedModel['mode']).toBe('markdown'); + expect(mutatedModel.id).toBe(1); // Should keep original id + expect(mutatedModel.options).toEqual({ existingOption: true }); // Should keep existing options + }); + + it('Should work with empty originalOptions', () => { + const reactPlugin = getPanelPlugin({ id: 'text' }); + + const migrationData = { + autoMigrateFrom: 'text', + originalOptions: {}, + }; + + const mutatedModel: TestPanelModel = { + id: 1, + type: 'text', + options: {}, + fieldConfig: { defaults: {}, overrides: [] }, + }; + + // Should not throw + expect(() => { + getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + }).not.toThrow(); + }); + + it('Should spread originalOptions AND call onPanelTypeChanged if plugin has both', () => { + let handlerCalled = false; + const onPanelTypeChanged: PanelTypeChangedHandler = (panel, prevPluginId, prevOptions) => { + handlerCalled = true; + // Verify originalOptions were already spread onto panel before handler is called + expect((panel as TestPanelModel)['customProp']).toBe('custom value'); + return { migrated: true }; + }; + + const reactPlugin = getPanelPlugin({ id: 'custom-panel' }).setPanelChangeHandler(onPanelTypeChanged); + + const migrationData = { + autoMigrateFrom: 'custom-panel', + originalOptions: { + customProp: 'custom value', + }, + }; + + const mutatedModel: TestPanelModel = { + id: 1, + type: 'custom-panel', + options: {}, + fieldConfig: { defaults: {}, overrides: [] }, + }; + + getV2AngularMigrationHandler(migrationData)(mutatedModel, reactPlugin); + + // Verify both behaviors occurred + expect(mutatedModel['customProp']).toBe('custom value'); // originalOptions spread + expect(handlerCalled).toBe(true); // onPanelTypeChanged called + expect(mutatedModel.options).toEqual({ migrated: true }); // handler result applied + }); + }); }); diff --git a/public/app/features/dashboard-scene/serialization/angularMigration.ts b/public/app/features/dashboard-scene/serialization/angularMigration.ts index 54b47081080..c0751c802d9 100644 --- a/public/app/features/dashboard-scene/serialization/angularMigration.ts +++ b/public/app/features/dashboard-scene/serialization/angularMigration.ts @@ -77,6 +77,11 @@ export function getAngularPanelMigrationHandler(oldModel: PanelModel) { * 4. Handler calls stat plugin's onPanelTypeChanged with { angular: originalOptions } * 5. Plugin migrates format/valueName/etc to proper stat options * + * For panels where autoMigrateFrom equals the current type (e.g., "text" -> "text"): + * - These are panels with Angular-style properties at the root level (content, mode, etc.) + * - We spread originalOptions onto the panel so the plugin's migration handler can see them + * - This matches the v1 behavior where PanelModel.restoreModel() spreads all properties + * * @param migrationData The __angularMigration data extracted from panel options */ export function getV2AngularMigrationHandler(migrationData: AngularMigrationData) { @@ -90,19 +95,28 @@ export function getV2AngularMigrationHandler(migrationData: AngularMigrationData return; } - if (plugin.onPanelTypeChanged) { - // Some plugins rely on being able to access targets to set up the fieldConfig when migrating from angular. - // Proxy the targets property with a deprecation warning. - const targetClone = cloneDeep(panel.targets); - Object.defineProperty(panel, 'targets', { - get: function () { - console.warn( - 'Accessing the targets property when migrating a panel plugin is deprecated. Changes to this property will be ignored.' - ); - return targetClone; - }, - }); + // Spread originalOptions onto the panel object. + // This is critical for plugins that use setMigrationHandler (like text panel) which expect + // Angular properties (content, mode, etc.) to be directly on the panel object. + // This matches the v1 behavior where PanelModel.restoreModel() spreads all JSON properties. + if (originalOptions && Object.keys(originalOptions).length > 0) { + defaults(panel, originalOptions); + } + // Some plugins rely on being able to access targets to set up the fieldConfig when migrating from angular. + // Proxy the targets property with a deprecation warning. + const targetClone = cloneDeep(panel.targets); + Object.defineProperty(panel, 'targets', { + get: function () { + console.warn( + 'Accessing the targets property when migrating a panel plugin is deprecated. Changes to this property will be ignored.' + ); + return targetClone; + }, + }); + + // For panels with onPanelTypeChanged (e.g., singlestat -> stat), call the handler + if (plugin.onPanelTypeChanged) { // For Angular panels, wrap in { angular: ... } to match expected format // For React panels migrating from other React panels, pass options directly const prevOptions = wasAngular ? { angular: originalOptions } : { options: originalOptions }; diff --git a/public/app/features/dashboard/api/ResponseTransformers.ts b/public/app/features/dashboard/api/ResponseTransformers.ts index 5a536074efe..9d68b84e2e4 100644 --- a/public/app/features/dashboard/api/ResponseTransformers.ts +++ b/public/app/features/dashboard/api/ResponseTransformers.ts @@ -609,17 +609,25 @@ export function buildPanelKind(p: Panel): PanelKind { // Build options with Angular migration data if needed (matches backend behavior) // autoMigrateFrom is set during v0->v1 migration when Angular panels are converted - const { autoMigrateFrom } = p; + let { autoMigrateFrom } = p; let options = p.options ?? {}; + const originalOptions = extractAngularOptions(p); + + // When autoMigrateFrom is present OR when there are Angular-specific properties at root level, + // compose __angularMigration with only Angular-specific options. + // This filters out known Panel schema properties, passing only the Angular options to migration handlers. + // The second condition handles panels like "text" that have Angular-style properties (content, mode) + // but don't have autoMigrateFrom set because they don't need a type conversion. + if (!autoMigrateFrom && Object.keys(originalOptions).length > 0) { + autoMigrateFrom = p.type; + } - // When autoMigrateFrom is present, compose __angularMigration with only Angular-specific options - // This filters out known Panel schema properties, passing only the Angular options to migration handlers if (autoMigrateFrom) { options = { ...options, __angularMigration: { autoMigrateFrom, - originalOptions: extractAngularOptions(p), + originalOptions, }, }; } From 58a026b6a5bc0e869430c05829756e868ffe4d1b Mon Sep 17 00:00:00 2001 From: Yunwen Zheng Date: Thu, 18 Dec 2025 10:22:40 -0500 Subject: [PATCH 228/297] RecentlyViewedDashboards: Clear history button (#115519) * RecentlyViewedDashboards: Clear history button --- .../components/RecentlyViewedDashboards.tsx | 45 ++++++++++++++----- public/app/plugins/panel/dashlist/styles.ts | 2 +- public/locales/en-US/grafana.json | 1 + 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx b/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx index ebfbd1d0899..7b2d58fa423 100644 --- a/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx +++ b/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx @@ -1,10 +1,12 @@ import { css } from '@emotion/css'; -import { useAsync } from 'react-use'; +import { useState } from 'react'; +import { useAsyncRetry } from 'react-use'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, store } from '@grafana/data'; import { t, Trans } from '@grafana/i18n'; import { evaluateBooleanFlag } from '@grafana/runtime/internal'; -import { CollapsableSection, Grid, Spinner, Text, useStyles2 } from '@grafana/ui'; +import { Button, CollapsableSection, Spinner, Stack, Text, useStyles2, Grid } from '@grafana/ui'; +import { contextSrv } from 'app/core/services/context_srv'; import { useDashboardLocationInfo } from 'app/features/search/hooks/useDashboardLocationInfo'; import { DashListItem } from 'app/plugins/panel/dashlist/DashListItem'; @@ -12,10 +14,18 @@ import { getRecentlyViewedDashboards } from './utils'; const MAX_RECENT = 5; +const recentDashboardsKey = `dashboard_impressions-${contextSrv.user.orgId}`; + export function RecentlyViewedDashboards() { + const [isOpen, setIsOpen] = useState(true); + const styles = useStyles2(getStyles); - const { value: recentDashboards = [], loading } = useAsync(async () => { + const { + value: recentDashboards = [], + loading, + retry, + } = useAsyncRetry(async () => { if (!evaluateBooleanFlag('recentlyViewedDashboards', false)) { return []; } @@ -23,6 +33,11 @@ export function RecentlyViewedDashboards() { }, []); const { foldersByUid } = useDashboardLocationInfo(recentDashboards.length > 0); + const handleClearHistory = () => { + store.set(recentDashboardsKey, JSON.stringify([])); + retry(); + }; + if (!evaluateBooleanFlag('recentlyViewedDashboards', false)) { return null; } @@ -31,11 +46,19 @@ export function RecentlyViewedDashboards() { - Recently viewed - + + setIsOpen(!isOpen)}> + Recently viewed + + + } - isOpen={true} + isOpen={isOpen} + // passing empty function to disable controlled mode, we only want to control isOpen when click on title + // this avoid entire header section being clickable which can be confusing with the Clear history button + onToggle={() => {}} className={styles.title} contentClassName={styles.content} > @@ -71,14 +94,16 @@ export function RecentlyViewedDashboards() { const getStyles = (theme: GrafanaTheme2) => { return { title: css({ - '& button svg': { + cursor: 'default', + '& [id^="collapse-button-"] svg': { color: theme.colors.primary.text, }, h3: { - background: `linear-gradient(90deg, ${theme.colors.primary.text} 0%, ${theme.colors.secondary.text} 100%)`, + background: `linear-gradient(90deg, ${theme.colors.primary.shade} 0%, ${theme.colors.primary.text} 100%)`, WebkitTextFillColor: 'transparent', backgroundClip: 'text', color: 'transparent', + cursor: 'pointer', }, }), content: css({ diff --git a/public/app/plugins/panel/dashlist/styles.ts b/public/app/plugins/panel/dashlist/styles.ts index 5e725daaf2d..c6346480c22 100644 --- a/public/app/plugins/panel/dashlist/styles.ts +++ b/public/app/plugins/panel/dashlist/styles.ts @@ -6,7 +6,7 @@ export const getStyles = (theme: GrafanaTheme2) => { const gradient = `linear-gradient( 90deg, ${colorManipulator.alpha(theme.colors.primary.text, 0.1)} 0%, - ${colorManipulator.alpha(theme.colors.secondary.text, 0.1)} 100% + ${colorManipulator.alpha(theme.colors.secondary.main, 0.1)} 100% )`; return { dashlistLink: css({ diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 25ad73abe1a..2e86a2a42d3 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -3717,6 +3717,7 @@ "text": "No results found for your query" }, "recently-viewed": { + "clear": "Clear history", "empty": "Nothing viewed yet", "title": "Recently viewed" }, From a65aa9d18fa5858359c09d2efba5b6e58935247a Mon Sep 17 00:00:00 2001 From: Vardan Torosyan Date: Thu, 18 Dec 2025 16:26:56 +0100 Subject: [PATCH 229/297] SCIM Docs: Replace warning with an information text for SAML identifier (#115353) * SCIM Docs: Replace warning with an information text for SAML identifier * Fix externalId warning --- .../configure-scim-provisioning/_index.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/docs/sources/setup-grafana/configure-access/configure-scim-provisioning/_index.md b/docs/sources/setup-grafana/configure-access/configure-scim-provisioning/_index.md index 9ef98bbc433..a07dde69e08 100644 --- a/docs/sources/setup-grafana/configure-access/configure-scim-provisioning/_index.md +++ b/docs/sources/setup-grafana/configure-access/configure-scim-provisioning/_index.md @@ -54,18 +54,6 @@ SCIM offers several advantages for managing users and teams in Grafana: ## Authentication and access requirements -{{< admonition type="warning" title="Critical: Aligning SAML Identifier with SCIM externalId" >}} -When using SAML for authentication alongside SCIM provisioning, a critical security measure is to ensure proper alignment between the the SCIM user's `externalId` and the SAML user identifier. The unique identifier used for SCIM provisioning (which becomes the `externalId` in Grafana, often sourced from a stable IdP attribute like Entra ID's `user.objectid`) **must also be sent as a claim in the SAML assertion from your Identity Provider.** -Furthermore, the Grafana SAML configuration must be correctly set up to identify and use this specific claim for linking the authenticated SAML user to their SCIM-provisioned user. This can be achieved by either ensuring the primary SAML login identifier by using the `assertion_attribute_external_uid` setting in Grafana to explicitly set the name of the SAML claim that contains the stable unique identifier attribute. - -**Why is this important?** -A mismatch or inconsistent mapping between this SAML login identifier and the SCIM `externalId` creates a critical security vulnerability. If these two identifiers are not reliably and uniquely aligned for each individual user, Grafana may fail to correctly link an authenticated SAML session to the intended SCIM-provisioned user profile and its associated permissions. This can enable a malicious actor to impersonate another user—for instance, by crafting a SAML assertion that, due to the identifier misalignment, incorrectly grants them the access rights of the targeted user. - -Grafana relies on this linkage to correctly associate the authenticated user from SAML with the provisioned user from SCIM. Failure to ensure a consistent and unique identifier across both systems can break this linkage, leading to incorrect user mapping and potential unauthorized access. - -Always verify that your SAML identity provider is configured to send a stable, unique user identifier that your SCIM configuration maps to `externalId`. Refer to your identity provider's documentation and the specific Grafana SCIM integration guides (e.g., for [Entra ID](configure-scim-with-azuread/) or [Okta](configure-scim-with-okta/)) for detailed instructions on configuring these attributes correctly. -{{< /admonition >}} - When you enable SCIM in Grafana, the following requirements and restrictions apply: 1. **Use the same identity provider for user provisioning and for authentication flow**: You must use the same identity provider for both authentication and user provisioning. @@ -74,6 +62,12 @@ When you enable SCIM in Grafana, the following requirements and restrictions app - Configure `userUID` SAML assertion in [Entra ID](/docs/grafana//setup-grafana/configure-access/configure-authentication/saml/configure-saml-with-azuread/#configure-saml-assertions-when-using-scim-provisioning) - Configure `userUID` SAML assertion in [Okta](/docs/grafana//setup-grafana/configure-access/configure-authentication/saml/configure-saml-with-okta/#configure-saml-assertions-when-using-scim-provisioning) +### Align SAML identifier with SCIM `externalId` + +When you use SAML with SCIM provisioning, align the SCIM `externalId` with the SAML user identifier. Use a stable IdP attribute (for example, Entra ID `user.objectid`) as the SCIM `externalId`, and send that same value as a SAML claim. Configure Grafana to read this claim with the `assertion_attribute_external_uid` setting so SAML authentication links to the SCIM-provisioned user and its permissions. + +If the SAML identifier and SCIM `externalId` differ, Grafana may not link the authenticated user to the intended SCIM profile, which can result in incorrect access. Verify your IdP sends a stable, unique identifier and that it matches the SCIM `externalId`. Refer to your IdP docs and the Grafana SCIM integration guides for [Entra ID](configure-scim-with-azuread/) and [Okta](configure-scim-with-okta/) for attribute configuration details. + ## Configure SCIM using the Grafana user interface You can configure SCIM in Grafana using the Grafana user interface. To do this, navigate to **Administration > Authentication > SCIM**. From 98aa6c50dc6ef8979c2f8e5e9f4fee4a944a40f1 Mon Sep 17 00:00:00 2001 From: Alexa Vargas <239999+axelavargas@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:42:37 +0100 Subject: [PATCH 230/297] DashboardLibrary: Force v1 dashboard scene page manager when loading template dashboards (#115488) Force v1 manager for template dashboards feature --- .../DashboardScenePageStateManager.test.ts | 117 ++++++++++++++++++ .../pages/DashboardScenePageStateManager.ts | 6 + 2 files changed, 123 insertions(+) diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts index 00de1c7ea13..b1857c9f01e 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts @@ -1673,6 +1673,123 @@ describe('UnifiedDashboardScenePageStateManager', () => { expect(manager2['activeManager']).toBeInstanceOf(DashboardScenePageStateManagerV2); }); }); + + describe('Template dashboards', () => { + let originalGetLocation: typeof locationService.getLocation; + + beforeEach(() => { + originalGetLocation = locationService.getLocation; + + // Mock window.location.search for loadDashboardLibrary + Object.defineProperty(window, 'location', { + value: { + search: '?gnetId=969&datasource=xpyRJd9Mz&mappings=%5B%5D', + }, + writable: true, + }); + }); + + afterEach(() => { + locationService.getLocation = originalGetLocation; + }); + + it('should always use v1 manager for template dashboards even when dashboardNewLayouts is enabled', async () => { + config.featureToggles.dashboardNewLayouts = true; + config.featureToggles.suggestedDashboards = true; + + // Mock location service with gnetId and mappings parameters + locationService.getLocation = jest.fn().mockReturnValue({ + pathname: '/dashboard/template', + search: '?gnetId=969&datasource=xpyRJd9Mz&mappings=%5B%5D', + }); + + // Mock the backend to return a community dashboard from grafana.com + setBackendSrv({ + get: jest.fn((url: string) => { + if (url.includes('/api/gnet/dashboards/')) { + return Promise.resolve({ + json: { + title: 'AWS ElastiCache Redis', + uid: '', + panels: [], + schemaVersion: 40, + }, + }); + } + return Promise.reject(new Error('Not found')); + }), + post: jest.fn((url: string) => { + if (url === '/api/dashboards/interpolate') { + return Promise.resolve({ + title: 'AWS ElastiCache Redis', + uid: '', + panels: [], + schemaVersion: 40, + }); + } + return Promise.reject(new Error('Not found')); + }), + } as unknown as BackendSrv); + + const manager = new UnifiedDashboardScenePageStateManager({}); + expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManagerV2); + + await manager.loadDashboard({ uid: '', route: DashboardRoutes.Template }); + + // Should switch to V1 manager for template dashboards + expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManager); + }); + + it('should reset to V2 manager when loading a new dashboard after template', async () => { + config.featureToggles.dashboardNewLayouts = true; + config.featureToggles.suggestedDashboards = true; + + // Mock locationService for this test too + locationService.getLocation = jest.fn().mockReturnValue({ + pathname: '/dashboard/template', + search: '?gnetId=969&datasource=xpyRJd9Mz&mappings=%5B%5D', + }); + + // Mock the backend for template load + setBackendSrv({ + get: jest.fn((url: string) => { + if (url.includes('/api/gnet/dashboards/')) { + return Promise.resolve({ + json: { + title: 'Template Dashboard', + uid: '', + panels: [], + schemaVersion: 40, + }, + }); + } + return Promise.reject(new Error('Not found')); + }), + post: jest.fn((url: string) => { + if (url === '/api/dashboards/interpolate') { + return Promise.resolve({ + title: 'Template Dashboard', + uid: '', + panels: [], + schemaVersion: 40, + }); + } + return Promise.reject(new Error('Not found')); + }), + } as unknown as BackendSrv); + + const manager = new UnifiedDashboardScenePageStateManager({}); + + // Load template - forces V1 + await manager.loadDashboard({ uid: '', route: DashboardRoutes.Template }); + expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManager); + + // Load new dashboard - should reset to V2 based on shouldForceV2API() + await manager.loadDashboard({ uid: '', route: DashboardRoutes.New }); + // Should be back to V2 manager + expect(manager['activeManager']).toBeInstanceOf(DashboardScenePageStateManagerV2); + }); + }); }); const customHomeDashboardV1Spec = { diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index 3a1841e6b32..3cc0df33e8a 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -1082,6 +1082,12 @@ export class UnifiedDashboardScenePageStateManager extends DashboardScenePageSta const newDashboardVersion = shouldForceV2API() ? 'v2' : 'v1'; this.setActiveManager(newDashboardVersion); } + + // Template dashboards are currently in v1 schema format. + if (options.route === DashboardRoutes.Template) { + this.setActiveManager('v1'); + } + return this.withVersionHandling((manager) => manager.loadDashboard.call(this, options)); } From d0792ebe9715ed91851c6eaf5a6494485cfe37ef Mon Sep 17 00:00:00 2001 From: Charandas <542168+charandas@users.noreply.github.com> Date: Thu, 18 Dec 2025 07:44:33 -0800 Subject: [PATCH 231/297] Secrets: Add gRPC client retry with exp. backoff" (#115526) Provisioning: secrets decrypt client should retry with exponential backoff --- pkg/registry/apis/secret/decrypt/grpc_client.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/registry/apis/secret/decrypt/grpc_client.go b/pkg/registry/apis/secret/decrypt/grpc_client.go index f892de5ebe5..0612455c3d8 100644 --- a/pkg/registry/apis/secret/decrypt/grpc_client.go +++ b/pkg/registry/apis/secret/decrypt/grpc_client.go @@ -9,10 +9,13 @@ import ( "maps" "os" "slices" + "time" "github.com/fullstorydev/grpchan" + grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" @@ -72,6 +75,15 @@ func NewGRPCDecryptClientWithTLS( opts = append(opts, grpc.WithDisableServiceConfig()) } + // Add retry interceptor to retry on transient connection issues. + // Retries on ResourceExhausted (per-RPC limits reached) and Unavailable (system unavailable). + retryInterceptor := grpc_retry.UnaryClientInterceptor( + grpc_retry.WithMax(3), + grpc_retry.WithBackoff(grpc_retry.BackoffExponentialWithJitter(time.Second, 0.5)), + grpc_retry.WithCodes(codes.ResourceExhausted, codes.Unavailable), + ) + opts = append(opts, grpc.WithUnaryInterceptor(retryInterceptor)) + conn, err := grpc.NewClient(address, opts...) if err != nil { return nil, fmt.Errorf("failed to connect to grpc decrypt server at %s: %w", address, err) From facb25a09c260bc52c301849cce82999aa8edbf3 Mon Sep 17 00:00:00 2001 From: Igor Suleymanov Date: Thu, 18 Dec 2025 18:07:48 +0200 Subject: [PATCH 232/297] Fix Grafana App SDK logger log level (#115551) * Fix Grafana App SDK logger log level What This commit fixes the hardcoded value of the app SDK logger log level by properly setting it during the log manager initialization. Why To prevent app SDK logging from always logging at DEBUG. Signed-off-by: Igor Suleymanov * Add missing argument to the logging test Signed-off-by: Igor Suleymanov --------- Signed-off-by: Igor Suleymanov --- pkg/infra/log/log.go | 31 +++++++++++++++++++++++-------- pkg/infra/log/log_test.go | 2 +- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/pkg/infra/log/log.go b/pkg/infra/log/log.go index 9e7a51a8dc7..abae387729e 100644 --- a/pkg/infra/log/log.go +++ b/pkg/infra/log/log.go @@ -54,7 +54,8 @@ func init() { } logger := level.NewFilter(format(os.Stderr), level.AllowInfo()) root = newManager(logger) - initAppSDKLogger(logger) + // Use default Info level during package initialization before config is loaded + initAppSDKLogger(logger, slog.LevelInfo) RegisterContextualLogProvider(func(ctx context.Context) ([]any, bool) { pFromCtx := ctx.Value(logParamsContextKey{}) @@ -80,7 +81,7 @@ func newManager(logger gokitlog.Logger) *logManager { } } -func (lm *logManager) initialize(loggers []logWithFilters) { +func (lm *logManager) initialize(loggers []logWithFilters, levelStr string) { lm.mutex.Lock() defer lm.mutex.Unlock() @@ -113,7 +114,7 @@ func (lm *logManager) initialize(loggers []logWithFilters) { lm.loggersByName[name].Swap(&compositeLogger{loggers: ctxLoggers}) } - initAppSDKLogger(lm.ConcreteLogger) + initAppSDKLogger(lm.ConcreteLogger, stringToSlogLevel(levelStr)) } func (lm *logManager) New(ctx ...any) *ConcreteLogger { @@ -514,7 +515,7 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) error { configLoggers = append(configLoggers, handler) } if len(configLoggers) > 0 { - root.initialize(configLoggers) + root.initialize(configLoggers, defaultLevelName) } return nil @@ -551,8 +552,22 @@ func SetupConsoleLogger(level string) error { return nil } -func initAppSDKLogger(gkl gokitlog.Logger) { - // We need to allow Debug logs here. go-kit/log does not support sharing the level we're using. - // TODO: Refactor such that we can pass in a level in a more appropriate manner. - logging.DefaultLogger = logging.NewSLogLogger(sloggokit.NewGoKitHandler(gkl, slog.LevelDebug)) +// stringToSlogLevel converts a log level string to slog.Level +func stringToSlogLevel(levelStr string) slog.Level { + switch strings.ToLower(levelStr) { + case "trace", "debug": + return slog.LevelDebug + case "info": + return slog.LevelInfo + case "warn", "warning": + return slog.LevelWarn + case "error", "critical": + return slog.LevelError + default: + return slog.LevelInfo + } +} + +func initAppSDKLogger(gkl gokitlog.Logger, level slog.Level) { + logging.DefaultLogger = logging.NewSLogLogger(sloggokit.NewGoKitHandler(gkl, level)) } diff --git a/pkg/infra/log/log_test.go b/pkg/infra/log/log_test.go index 98fee4be8c0..5302e565964 100644 --- a/pkg/infra/log/log_test.go +++ b/pkg/infra/log/log_test.go @@ -88,7 +88,7 @@ func TestNew(t *testing.T) { val: swapLogger, maxLevel: level.AllowAll(), }, - }) + }, "info") err := log1.Log("msg", "hello 1") require.NoError(t, err) From 19f6dbe1bb8f51e6cbfdedf74d905bb78900a308 Mon Sep 17 00:00:00 2001 From: Renato Costa <103441181+renatolabs@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:21:36 -0500 Subject: [PATCH 233/297] unified-storage: add `BatchGet` support to the sqlkv implementation (#115517) * unified-storage: add `BatchGet` support to the sqlkv implementation * address comments * fix linting --- .../unified/resource/data/sqlkv_batch_get.sql | 12 +++ pkg/storage/unified/resource/sqlkv.go | 78 +++++++++++++++++-- pkg/storage/unified/testing/kv.go | 75 +++++++++++------- pkg/storage/unified/testing/kv_test.go | 1 - 4 files changed, 131 insertions(+), 35 deletions(-) create mode 100644 pkg/storage/unified/resource/data/sqlkv_batch_get.sql diff --git a/pkg/storage/unified/resource/data/sqlkv_batch_get.sql b/pkg/storage/unified/resource/data/sqlkv_batch_get.sql new file mode 100644 index 00000000000..0babcb36970 --- /dev/null +++ b/pkg/storage/unified/resource/data/sqlkv_batch_get.sql @@ -0,0 +1,12 @@ +SELECT r.{{ .Ident "key_path" }}, r.{{ .Ident "value" }} +FROM ( +{{ range $id, $key_path := .KeyPaths }} + {{ if eq $id 0 }} + SELECT {{ $.Arg $id }} AS idx, {{ $.Arg $key_path }} AS key_path + {{ else }} + UNION ALL SELECT {{ $.Arg $id }}, {{ $.Arg $key_path }} + {{ end }} +{{ end }} +) AS requested_keys +INNER JOIN {{ .TableName }} r ON r.{{ .Ident "key_path" }} = requested_keys.{{ .Ident "key_path" }} +ORDER BY requested_keys.{{ .Ident "idx" }}; diff --git a/pkg/storage/unified/resource/sqlkv.go b/pkg/storage/unified/resource/sqlkv.go index a56d00a3e12..22cd3085122 100644 --- a/pkg/storage/unified/resource/sqlkv.go +++ b/pkg/storage/unified/resource/sqlkv.go @@ -34,9 +34,10 @@ func mustTemplate(filename string) *template.Template { // Templates. var ( - sqlKVGet = mustTemplate("sqlkv_get.sql") - sqlKVDelete = mustTemplate("sqlkv_delete.sql") - sqlKVKeys = mustTemplate("sqlkv_keys.sql") + sqlKVKeys = mustTemplate("sqlkv_keys.sql") + sqlKVGet = mustTemplate("sqlkv_get.sql") + sqlKVBatchGet = mustTemplate("sqlkv_batch_get.sql") + sqlKVDelete = mustTemplate("sqlkv_delete.sql") ) // sqlKVSection can be embedded in structs used when rendering query templates @@ -107,13 +108,23 @@ func (req sqlKVGetRequest) Results() ([]byte, error) { return req.Value, nil } -type sqlKVDeleteRequest struct { +type sqlKVBatchGetRequest struct { sqltemplate.SQLTemplate - sqlKVSectionKey + sqlKVSection + Keys []string } -func (req sqlKVDeleteRequest) Validate() error { - return req.sqlKVSectionKey.Validate() +func (req sqlKVBatchGetRequest) Validate() error { + return req.sqlKVSection.Validate() +} + +func (req sqlKVBatchGetRequest) KeyPaths() []string { + result := make([]string, 0, len(req.Keys)) + for _, key := range req.Keys { + result = append(result, req.Section+"/"+key) + } + + return result } type sqlKVKeysRequest struct { @@ -142,6 +153,15 @@ func (req sqlKVKeysRequest) SortAscending() bool { return req.Options.Sort != SortOrderDesc } +type sqlKVDeleteRequest struct { + sqltemplate.SQLTemplate + sqlKVSectionKey +} + +func (req sqlKVDeleteRequest) Validate() error { + return req.sqlKVSectionKey.Validate() +} + var _ KV = &sqlKV{} type sqlKV struct { @@ -188,6 +208,7 @@ func (k *sqlKV) Keys(ctx context.Context, section string, opt ListOptions) iter. yield("", err) return } + defer closeRows(rows, yield) for rows.Next() { var key string @@ -225,7 +246,41 @@ func (k *sqlKV) Get(ctx context.Context, section string, key string) (io.ReadClo func (k *sqlKV) BatchGet(ctx context.Context, section string, keys []string) iter.Seq2[KeyValue, error] { return func(yield func(KeyValue, error) bool) { - panic("not implemented!") + if len(keys) == 0 { + return + } + + rows, err := dbutil.QueryRows(ctx, k.db, sqlKVBatchGet, sqlKVBatchGetRequest{ + SQLTemplate: sqltemplate.New(k.dialect), + sqlKVSection: sqlKVSection{section}, + Keys: keys, + }) + if err != nil { + yield(KeyValue{}, err) + return + } + defer closeRows(rows, yield) + + for rows.Next() { + var key string + var value []byte + if err := rows.Scan(&key, &value); err != nil { + yield(KeyValue{}, fmt.Errorf("error reading row: %w", err)) + return + } + + kv := KeyValue{ + Key: strings.TrimPrefix(key, section+"/"), + Value: io.NopCloser(bytes.NewReader(value)), + } + if !yield(kv, nil) { + return + } + } + + if err := rows.Err(); err != nil { + yield(KeyValue{}, fmt.Errorf("failed to read rows: %w", err)) + } } } @@ -273,3 +328,10 @@ func (k *sqlKV) BatchDelete(ctx context.Context, section string, keys []string) func (k *sqlKV) UnixTimestamp(ctx context.Context) (int64, error) { panic("not implemented!") } + +func closeRows[T any](rows db.Rows, yield func(T, error) bool) { + if err := rows.Close(); err != nil { + var zero T + yield(zero, fmt.Errorf("error closing rows: %w", err)) + } +} diff --git a/pkg/storage/unified/testing/kv.go b/pkg/storage/unified/testing/kv.go index b4c61f84c13..fbd831f6281 100644 --- a/pkg/storage/unified/testing/kv.go +++ b/pkg/storage/unified/testing/kv.go @@ -103,6 +103,7 @@ func namespacedKey(nsPrefix, key string) string { func runTestKVGet(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + nsPrefix += "-get" t.Run("get existing key", func(t *testing.T) { // First save a key @@ -221,6 +222,7 @@ func runTestKVSave(t *testing.T, kv resource.KV, nsPrefix string) { func runTestKVDelete(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + nsPrefix += "-delete" t.Run("delete existing key", func(t *testing.T) { // First create a key @@ -262,6 +264,7 @@ func runTestKVDelete(t *testing.T, kv resource.KV, nsPrefix string) { func runTestKVKeys(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + nsPrefix += "-keys" // Setup test data testKeys := namespacedKeys(nsPrefix, []string{"a1", "a2", "b1", "b2", "c1"}) @@ -360,6 +363,7 @@ func runTestKVKeys(t *testing.T, kv resource.KV, nsPrefix string) { func runTestKVKeysWithLimits(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + nsPrefix += "-keys-with-limits" // Setup test data testKeys := namespacedKeys(nsPrefix, []string{"a1", "a2", "b1", "b2", "c1", "c2", "d1", "d2"}) @@ -416,6 +420,7 @@ func runTestKVKeysWithLimits(t *testing.T, kv resource.KV, nsPrefix string) { func runTestKVKeysWithSort(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + nsPrefix += "-keys-with-sort" // Setup test data testKeys := namespacedKeys(nsPrefix, []string{"a1", "a2", "b1", "b2", "c1"}) @@ -619,29 +624,29 @@ func runTestKVUnixTimestamp(t *testing.T, kv resource.KV, nsPrefix string) { func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) - section := nsPrefix + "-batchget" + nsPrefix += "-batchget" t.Run("batch get existing keys", func(t *testing.T) { // Setup test data testData := map[string]string{ - "key1": "value1", - "key2": "value2", - "key3": "value3", + namespacedKey(nsPrefix, "key1"): "value1", + namespacedKey(nsPrefix, "key2"): "value2", + namespacedKey(nsPrefix, "key3"): "value3", } // Save test data for key, value := range testData { - saveKVHelper(t, kv, ctx, section, key, strings.NewReader(value)) + saveKVHelper(t, kv, ctx, testSection, key, strings.NewReader(value)) } // Batch get all keys - keys := []string{"key1", "key2", "key3"} + keys := namespacedKeys(nsPrefix, []string{"key1", "key2", "key3"}) type result struct { key string value string } var results []result - for kv, err := range kv.BatchGet(ctx, section, keys) { + for kv, err := range kv.BatchGet(ctx, testSection, keys) { require.NoError(t, err) value, err := io.ReadAll(kv.Value) require.NoError(t, err) @@ -651,10 +656,10 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { } // Verify results - assert.Len(t, results, 3) + require.Len(t, results, 3) // Check that all keys are present and in order - expectedKeys := []string{"key1", "key2", "key3"} + expectedKeys := namespacedKeys(nsPrefix, []string{"key1", "key2", "key3"}) actualKeys := make([]string, len(results)) for i, r := range results { actualKeys[i] = r.key @@ -663,22 +668,40 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { // Verify values for _, r := range results { - assert.Equal(t, testData[r.key], r.value) + assert.Equal(t, testData[r.key], r.value, "key = %s", r.key) } }) + t.Run("batch get with empty section", func(t *testing.T) { + var kvs []resource.KeyValue + var errs []error + keys := namespacedKeys(nsPrefix, []string{"key1", "key2", "key3"}) + for kv, err := range kv.BatchGet(ctx, "", keys) { + if err != nil { + errs = append(errs, err) + continue + } + + kvs = append(kvs, kv) + } + + require.Len(t, errs, 1) + assert.Contains(t, errs[0].Error(), "section is required") + assert.Empty(t, kvs) + }) + t.Run("batch get with non-existent keys", func(t *testing.T) { // Setup some test data - saveKVHelper(t, kv, ctx, section, "existing-key", strings.NewReader("existing-value")) + saveKVHelper(t, kv, ctx, testSection, namespacedKey(nsPrefix, "existing-key"), strings.NewReader("existing-value")) // Batch get with mix of existing and non-existent keys - keys := []string{"existing-key", "non-existent-1", "non-existent-2"} + keys := namespacedKeys(nsPrefix, []string{"existing-key", "non-existent-1", "non-existent-2"}) type result struct { key string value string } var results []result - for kv, err := range kv.BatchGet(ctx, section, keys) { + for kv, err := range kv.BatchGet(ctx, testSection, keys) { require.NoError(t, err) value, err := io.ReadAll(kv.Value) require.NoError(t, err) @@ -688,15 +711,15 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { } // Should only return the existing key - assert.Len(t, results, 1) - assert.Equal(t, "existing-key", results[0].key) + require.Len(t, results, 1) + assert.Equal(t, namespacedKey(nsPrefix, "existing-key"), results[0].key) assert.Equal(t, "existing-value", results[0].value) }) t.Run("batch get with all non-existent keys", func(t *testing.T) { - keys := []string{"non-existent-1", "non-existent-2", "non-existent-3"} + keys := namespacedKeys(nsPrefix, []string{"non-existent-1", "non-existent-2", "non-existent-3"}) var results []resource.KeyValue - for kv, err := range kv.BatchGet(ctx, section, keys) { + for kv, err := range kv.BatchGet(ctx, testSection, keys) { require.NoError(t, err) results = append(results, kv) } @@ -708,7 +731,7 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { t.Run("batch get with empty keys list", func(t *testing.T) { keys := []string{} var results []resource.KeyValue - for kv, err := range kv.BatchGet(ctx, section, keys) { + for kv, err := range kv.BatchGet(ctx, testSection, keys) { require.NoError(t, err) results = append(results, kv) } @@ -718,16 +741,16 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { }) t.Run("batch get with empty section", func(t *testing.T) { - keys := []string{"some-key"} + keys := namespacedKeys(nsPrefix, []string{"some-key"}) var errors []error for kv, err := range kv.BatchGet(ctx, "", keys) { if err != nil { errors = append(errors, err) - break + continue } _ = kv // unused } - assert.Len(t, errors, 1) + require.Len(t, errors, 1) assert.Contains(t, errors[0].Error(), "section is required") }) @@ -741,13 +764,13 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { // Save test data for key, value := range testData { - saveKVHelper(t, kv, ctx, section, key, strings.NewReader(value)) + saveKVHelper(t, kv, ctx, testSection, namespacedKey(nsPrefix, key), strings.NewReader(value)) } // Batch get in specific order - keys := []string{"z-key", "a-key", "m-key"} + keys := namespacedKeys(nsPrefix, []string{"z-key", "invalid-key1", "a-key", "invalid-key2", "m-key", "invalid-key3"}) var results []string - for kv, err := range kv.BatchGet(ctx, section, keys) { + for kv, err := range kv.BatchGet(ctx, testSection, keys) { require.NoError(t, err) err = kv.Value.Close() require.NoError(t, err) @@ -755,8 +778,8 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { } // Verify order is preserved - assert.Len(t, results, 3) - expectedOrder := []string{"z-key", "a-key", "m-key"} + require.Len(t, results, 3) + expectedOrder := namespacedKeys(nsPrefix, []string{"z-key", "a-key", "m-key"}) assert.Equal(t, expectedOrder, results) }) } diff --git a/pkg/storage/unified/testing/kv_test.go b/pkg/storage/unified/testing/kv_test.go index 780f36fcc66..dafefc15ed2 100644 --- a/pkg/storage/unified/testing/kv_test.go +++ b/pkg/storage/unified/testing/kv_test.go @@ -50,7 +50,6 @@ func TestSQLKV(t *testing.T) { TestKVSave: true, TestKVConcurrent: true, TestKVUnixTimestamp: true, - TestKVBatchGet: true, TestKVBatchDelete: true, }, }) From 1862e5dac56c018afed1e55631cd48885214eafc Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Thu, 18 Dec 2025 18:54:55 +0200 Subject: [PATCH 234/297] IAM: Fix team search for unistore (#115250) * fix team search for unistore * fix search in unistore * remove field prefix when generating the response * fix unit test * address feedback --- pkg/registry/apis/iam/team/legacy_search.go | 7 ++++-- pkg/registry/apis/iam/team_search.go | 23 +++++++++++++++---- pkg/registry/apis/iam/team_search_test.go | 2 +- .../unified/search/builders/team_search.go | 4 ++-- .../apis/iam/team_search_integration_test.go | 5 ++-- 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/pkg/registry/apis/iam/team/legacy_search.go b/pkg/registry/apis/iam/team/legacy_search.go index 938062bd9e6..8f9594b96ac 100644 --- a/pkg/registry/apis/iam/team/legacy_search.go +++ b/pkg/registry/apis/iam/team/legacy_search.go @@ -6,6 +6,7 @@ import ( "log/slog" "math" "strconv" + "strings" "google.golang.org/grpc" @@ -102,7 +103,8 @@ func getColumns(fields []string) []*resourcepb.ResourceTableColumnDefinition { columns := getDefaultColumns() for _, field := range fields { - if col, ok := builders.TeamSearchTableColumnDefinitions[field]; ok { + fieldName := strings.TrimPrefix(field, res.SEARCH_FIELD_PREFIX) + if col, ok := builders.TeamSearchTableColumnDefinitions[fieldName]; ok { columns = append(columns, col) } } @@ -121,7 +123,8 @@ func getDefaultColumns() []*resourcepb.ResourceTableColumnDefinition { func createCells(t *team.TeamDTO, fields []string) [][]byte { cells := createDefaultCells(t) for _, field := range fields { - switch field { + fieldName := strings.TrimPrefix(field, res.SEARCH_FIELD_PREFIX) + switch fieldName { case builders.TEAM_SEARCH_EMAIL: cells = append(cells, []byte(t.Email)) case builders.TEAM_SEARCH_PROVISIONED: diff --git a/pkg/registry/apis/iam/team_search.go b/pkg/registry/apis/iam/team_search.go index 786bf9be834..2252263a708 100644 --- a/pkg/registry/apis/iam/team_search.go +++ b/pkg/registry/apis/iam/team_search.go @@ -2,6 +2,7 @@ package iam import ( "encoding/json" + "fmt" "net/http" "net/url" "strconv" @@ -12,6 +13,7 @@ import ( "k8s.io/kube-openapi/pkg/validation/spec" iamv0alpha1 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -137,6 +139,12 @@ func (s *TeamSearchHandler) DoTeamSearch(w http.ResponseWriter, r *http.Request) return } + requester, err := identity.GetRequester(ctx) + if err != nil { + errhttp.Write(ctx, fmt.Errorf("no identity found for request: %w", err), w) + return + } + limit := 50 offset := 0 page := 1 @@ -154,16 +162,23 @@ func (s *TeamSearchHandler) DoTeamSearch(w http.ResponseWriter, r *http.Request) } searchRequest := &resourcepb.ResourceSearchRequest{ - Options: &resourcepb.ListOptions{}, + Options: &resourcepb.ListOptions{ + Key: &resourcepb.ResourceKey{ + Group: iamv0alpha1.TeamResourceInfo.GroupResource().Group, + Resource: iamv0alpha1.TeamResourceInfo.GroupResource().Resource, + Namespace: requester.GetNamespace(), + }, + }, Query: queryParams.Get("query"), Limit: int64(limit), Offset: int64(offset), Page: int64(page), Explain: queryParams.Has("explain") && queryParams.Get("explain") != "false", Fields: []string{ - builders.TEAM_SEARCH_EMAIL, - builders.TEAM_SEARCH_PROVISIONED, - builders.TEAM_SEARCH_EXTERNAL_UID, + resource.SEARCH_FIELD_TITLE, + resource.SEARCH_FIELD_PREFIX + builders.TEAM_SEARCH_EMAIL, + resource.SEARCH_FIELD_PREFIX + builders.TEAM_SEARCH_PROVISIONED, + resource.SEARCH_FIELD_PREFIX + builders.TEAM_SEARCH_EXTERNAL_UID, }, } diff --git a/pkg/registry/apis/iam/team_search_test.go b/pkg/registry/apis/iam/team_search_test.go index 76efed1c067..2142ba4e505 100644 --- a/pkg/registry/apis/iam/team_search_test.go +++ b/pkg/registry/apis/iam/team_search_test.go @@ -89,7 +89,7 @@ func TestTeamSearchHandler(t *testing.T) { if mockClient.LastSearchRequest == nil { t.Fatalf("expected Search to be called, but it was not") } - expectedFields := []string{"email", "provisioned", "externalUID"} + expectedFields := []string{"title", "fields.email", "fields.provisioned", "fields.externalUID"} if fmt.Sprintf("%v", mockClient.LastSearchRequest.Fields) != fmt.Sprintf("%v", expectedFields) { t.Errorf("expected fields %v, got %v", expectedFields, mockClient.LastSearchRequest.Fields) } diff --git a/pkg/storage/unified/search/builders/team_search.go b/pkg/storage/unified/search/builders/team_search.go index 4b09074b3dc..3dcf3cfdc95 100644 --- a/pkg/storage/unified/search/builders/team_search.go +++ b/pkg/storage/unified/search/builders/team_search.go @@ -46,8 +46,8 @@ func GetTeamSearchBuilder() (resource.DocumentBuilderInfo, error) { return resource.DocumentBuilderInfo{ GroupResource: schema.GroupResource{ - Group: "iam.grafana.app", - Resource: "searchTeams", + Group: v0alpha1.TeamResourceInfo.GroupResource().Group, + Resource: v0alpha1.TeamResourceInfo.GroupResource().Resource, }, Fields: fields, Builder: new(teamSearchBuilder), diff --git a/pkg/tests/apis/iam/team_search_integration_test.go b/pkg/tests/apis/iam/team_search_integration_test.go index c01ca9a641a..bf02f5718c9 100644 --- a/pkg/tests/apis/iam/team_search_integration_test.go +++ b/pkg/tests/apis/iam/team_search_integration_test.go @@ -21,8 +21,7 @@ import ( func TestIntegrationTeamSearch(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) - // TODO: Add rest.Mode3 and rest.Mode4 when they're supported - modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2} + modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3, rest.Mode4, rest.Mode5} for _, mode := range modes { t.Run(fmt.Sprintf("Team search with dual writer mode %d", mode), func(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ @@ -38,6 +37,7 @@ func TestIntegrationTeamSearch(t *testing.T) { featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagKubernetesAuthnMutation, }, + UnifiedStorageEnableSearch: true, }) doTeamSearchTests(t, helper) }) @@ -59,7 +59,6 @@ func doTeamSearchTests(t *testing.T, helper *apis.K8sTestHelper) { require.NoError(t, err) require.NotNil(t, team1) - // Create a second team with a different name team2YAML := helper.LoadYAMLOrJSONFile("testdata/team-test-create-v0.yaml") team2YAML.Object["metadata"].(map[string]interface{})["name"] = "testteam2" team2YAML.Object["spec"].(map[string]interface{})["title"] = "Another Team" From 051cdaad0d3d5c2f1e9f976fb2f87d6d5c6baf7e Mon Sep 17 00:00:00 2001 From: Galen Kistler <109082771+gtk-grafana@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:11:33 -0600 Subject: [PATCH 235/297] Revert "Plugins: Add PluginInsights UI (#111603)" (#115574) This reverts commit 1f4f2b4d7c6af8fb32fbba448a95d85e8886d632. --- eslint-suppressions.json | 5 + .../src/types/featureToggles.gen.ts | 5 - pkg/services/featuremgmt/registry.go | 8 - pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.json | 14 -- public/app/features/plugins/admin/api.ts | 16 -- .../components/PluginDetailsPage.test.tsx | 2 - .../admin/components/PluginDetailsPage.tsx | 21 +-- .../components/PluginDetailsPanel.test.tsx | 71 +------- .../admin/components/PluginDetailsPanel.tsx | 9 +- .../admin/components/PluginInsights.test.tsx | 171 ------------------ .../admin/components/PluginInsights.tsx | 140 -------------- .../plugins/admin/mocks/catalogPlugin.mock.ts | 2 - .../plugins/admin/mocks/mockHelpers.ts | 8 - .../features/plugins/admin/state/actions.ts | 20 +- .../app/features/plugins/admin/state/hooks.ts | 29 +-- .../features/plugins/admin/state/reducer.ts | 5 - public/app/features/plugins/admin/types.ts | 49 ----- public/locales/en-US/grafana.json | 6 - 19 files changed, 16 insertions(+), 566 deletions(-) delete mode 100644 public/app/features/plugins/admin/components/PluginInsights.test.tsx delete mode 100644 public/app/features/plugins/admin/components/PluginInsights.tsx diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 80d18ce0cae..1cdf9de4c4a 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -2868,6 +2868,11 @@ "count": 1 } }, + "public/app/features/plugins/admin/components/PluginDetailsPage.tsx": { + "@typescript-eslint/consistent-type-assertions": { + "count": 1 + } + }, "public/app/features/plugins/admin/helpers.ts": { "no-restricted-syntax": { "count": 2 diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 6ce08b92452..b91161d966c 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -1189,11 +1189,6 @@ export interface FeatureToggles { */ onlyStoreActionSets?: boolean; /** - * Show insights for plugins in the plugin details page - * @default false - */ - pluginInsights?: boolean; - /** * Enables a new panel time settings drawer */ panelTimeSettings?: boolean; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index af82c05e61d..d8a46601ad6 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1960,14 +1960,6 @@ var ( Owner: identityAccessTeam, Expression: "true", }, - { - Name: "pluginInsights", - Description: "Show insights for plugins in the plugin details page", - Stage: FeatureStageExperimental, - FrontendOnly: true, - Owner: grafanaPluginsPlatformSquad, - Expression: "false", - }, { Name: "panelTimeSettings", Description: "Enables a new panel time settings drawer", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index fedcd197a1f..3fbc019bff1 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -266,7 +266,6 @@ jaegerEnableGrpcEndpoint,experimental,@grafana/oss-big-tent,false,false,false pluginStoreServiceLoading,experimental,@grafana/plugins-platform-backend,false,false,false newPanelPadding,preview,@grafana/dashboards-squad,false,false,true onlyStoreActionSets,GA,@grafana/identity-access-team,false,false,false -pluginInsights,experimental,@grafana/plugins-platform-backend,false,false,true panelTimeSettings,experimental,@grafana/dashboards-squad,false,false,false elasticsearchRawDSLQuery,experimental,@grafana/partner-datasources,false,false,false kubernetesAnnotations,experimental,@grafana/grafana-backend-services-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 3c33f362b29..866f8a78881 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2693,20 +2693,6 @@ "expression": "false" } }, - { - "metadata": { - "name": "pluginInsights", - "resourceVersion": "1761300628147", - "creationTimestamp": "2025-10-24T10:10:28Z" - }, - "spec": { - "description": "Show insights for plugins in the plugin details page", - "stage": "experimental", - "codeowner": "@grafana/plugins-platform-backend", - "frontend": true, - "expression": "false" - } - }, { "metadata": { "name": "pluginInstallAPISync", diff --git a/public/app/features/plugins/admin/api.ts b/public/app/features/plugins/admin/api.ts index aa5bc32f183..74a072ba054 100644 --- a/public/app/features/plugins/admin/api.ts +++ b/public/app/features/plugins/admin/api.ts @@ -8,7 +8,6 @@ import { LocalPlugin, RemotePlugin, CatalogPluginDetails, - CatalogPluginInsights, Version, PluginVersion, InstancePlugin, @@ -48,21 +47,6 @@ export async function getPluginDetails(id: string): Promise { - if (!version) { - throw new Error('Version is required'); - } - try { - const insights = await getBackendSrv().get(`${GCOM_API_ROOT}/plugins/${id}/versions/${version}/insights`); - return insights; - } catch (error) { - if (isFetchError(error)) { - error.isHandled = true; - } - throw error; - } -} - export async function getRemotePlugins(): Promise { try { const { items: remotePlugins }: { items: RemotePlugin[] } = await getBackendSrv().get(`${GCOM_API_ROOT}/plugins`, { diff --git a/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx b/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx index 0ffc93f8f77..da4eef2f0d4 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx @@ -62,12 +62,10 @@ const plugin: CatalogPlugin = { angularDetected: false, isFullyInstalled: true, accessControl: {}, - insights: { id: 1, name: 'test-plugin', version: '1.0.0', insights: [] }, }; jest.mock('../state/hooks', () => ({ useGetSingle: jest.fn(), - useGetPluginInsights: jest.fn(), useFetchStatus: jest.fn().mockReturnValue({ isLoading: false }), useFetchDetailsStatus: () => ({ isLoading: false }), useIsRemotePluginsAvailable: () => false, diff --git a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx index b135321e558..0e651a8e4bf 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx @@ -16,19 +16,11 @@ import { PluginDetailsPanel } from '../components/PluginDetailsPanel'; import { PluginDetailsSignature } from '../components/PluginDetailsSignature'; import { usePluginDetailsTabs } from '../hooks/usePluginDetailsTabs'; import { usePluginPageExtensions } from '../hooks/usePluginPageExtensions'; -import { useGetSingle, useFetchStatus, useFetchDetailsStatus, useGetPluginInsights } from '../state/hooks'; +import { useGetSingle, useFetchStatus, useFetchDetailsStatus } from '../state/hooks'; import { PluginTabIds } from '../types'; import { PluginDetailsDeprecatedWarning } from './PluginDetailsDeprecatedWarning'; -function isPluginTabId(value: string | null): value is PluginTabIds { - if (!value) { - return false; - } - const validIds: string[] = Object.values(PluginTabIds); - return validIds.includes(value); -} - export type Props = { // The ID of the plugin pluginId: string; @@ -57,13 +49,12 @@ export function PluginDetailsPage({ }; const queryParams = new URLSearchParams(location.search); const plugin = useGetSingle(pluginId); // fetches the plugin settings for this Grafana instance - useGetPluginInsights(pluginId, plugin?.isInstalled ? plugin?.installedVersion : plugin?.latestVersion); - const isNarrowScreen = useMedia('(max-width: 600px)'); - const pageParam = queryParams.get('page'); - const pageId = pageParam && isPluginTabId(pageParam) ? pageParam : undefined; - const { navModel, activePageId } = usePluginDetailsTabs(plugin, pageId, isNarrowScreen); - + const { navModel, activePageId } = usePluginDetailsTabs( + plugin, + queryParams.get('page') as PluginTabIds, + isNarrowScreen + ); const { actions, info, subtitle } = usePluginPageExtensions(plugin); const { isLoading: isFetchLoading } = useFetchStatus(); const { isLoading: isFetchDetailsLoading } = useFetchDetailsStatus(); diff --git a/public/app/features/plugins/admin/components/PluginDetailsPanel.test.tsx b/public/app/features/plugins/admin/components/PluginDetailsPanel.test.tsx index 20787099842..eade37f559c 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPanel.test.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPanel.test.tsx @@ -1,23 +1,11 @@ -import userEvent from '@testing-library/user-event'; import { render, screen } from 'test/test-utils'; import { PluginSignatureStatus, PluginSignatureType, PluginType } from '@grafana/data'; -import { config } from '@grafana/runtime'; -import { CatalogPlugin, SCORE_LEVELS } from '../types'; +import { CatalogPlugin } from '../types'; import { PluginDetailsPanel } from './PluginDetailsPanel'; -jest.mock('@grafana/runtime', () => ({ - ...jest.requireActual('@grafana/runtime'), - config: { - ...jest.requireActual('@grafana/runtime').config, - featureToggles: { - pluginInsights: false, - }, - }, -})); - const mockPlugin: CatalogPlugin = { description: 'Test plugin description', downloads: 1000, @@ -197,61 +185,4 @@ describe('PluginDetailsPanel', () => { expect(regularLinks).toContainElement(raiseIssueLink); expect(regularLinks).not.toContainElement(websiteLink); }); - - it('should render plugin insights when plugin has insights', async () => { - config.featureToggles.pluginInsights = true; - const pluginWithInsights = { - ...mockPlugin, - insights: { - id: 1, - name: 'test-plugin', - version: '1.0.0', - insights: [ - { - name: 'security', - scoreValue: 90, - scoreLevel: SCORE_LEVELS.EXCELLENT, - items: [ - { - id: 'signature', - name: 'Signature verified', - level: 'ok' as const, - }, - ], - }, - ], - }, - }; - render(); - expect(screen.getByTestId('plugin-insights-container')).toBeInTheDocument(); - expect(screen.getByText('Plugin insights')).toBeInTheDocument(); - expect(screen.queryByText('Security')).toBeInTheDocument(); - await userEvent.click(screen.getByText('Security')); - expect(screen.getByTestId('plugin-insight-item-signature')).toBeInTheDocument(); - }); - - it('should not render plugin insights when plugin has no insights', () => { - const pluginWithoutInsights = { - ...mockPlugin, - insights: undefined, - }; - render(); - expect(screen.queryByTestId('plugin-insights-container')).not.toBeInTheDocument(); - expect(screen.queryByText('Plugin insights')).not.toBeInTheDocument(); - }); - - it('should not render plugin insights when insights array is empty', () => { - const pluginWithEmptyInsights = { - ...mockPlugin, - insights: { - id: 1, - name: 'test-plugin', - version: '1.0.0', - insights: [], - }, - }; - render(); - expect(screen.queryByTestId('plugin-insights-container')).not.toBeInTheDocument(); - expect(screen.queryByText('Plugin insights')).not.toBeInTheDocument(); - }); }); diff --git a/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx b/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx index aa8b6c792ef..00211b61c6e 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { t, Trans } from '@grafana/i18n'; -import { config, reportInteraction } from '@grafana/runtime'; +import { reportInteraction } from '@grafana/runtime'; import { PageInfoItem } from '@grafana/runtime/internal'; import { Stack, @@ -22,8 +22,6 @@ import { formatDate } from 'app/core/internationalization/dates'; import { CatalogPlugin } from '../types'; -import { PluginInsights } from './PluginInsights'; - type Props = { pluginExtentionsInfo: PageInfoItem[]; plugin: CatalogPlugin; width?: string }; export function PluginDetailsPanel(props: Props): React.ReactElement | null { @@ -71,11 +69,6 @@ export function PluginDetailsPanel(props: Props): React.ReactElement | null { return ( <> - {config.featureToggles.pluginInsights && plugin.insights && plugin.insights?.insights?.length > 0 && ( - - - - )} {pluginExtentionsInfo.map((infoItem, index) => { diff --git a/public/app/features/plugins/admin/components/PluginInsights.test.tsx b/public/app/features/plugins/admin/components/PluginInsights.test.tsx deleted file mode 100644 index efd064c7172..00000000000 --- a/public/app/features/plugins/admin/components/PluginInsights.test.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import userEvent from '@testing-library/user-event'; -import { render, screen } from 'test/test-utils'; - -import { CatalogPluginInsights, InsightLevel, SCORE_LEVELS } from '../types'; - -import { PluginInsights } from './PluginInsights'; - -const mockPluginInsights: CatalogPluginInsights = { - id: 1, - name: 'test-plugin', - version: '1.0.0', - insights: [ - { - name: 'security', - scoreValue: 90, - scoreLevel: SCORE_LEVELS.EXCELLENT, - items: [ - { - id: 'signature', - name: 'Signature verified', - description: 'Plugin signature is valid', - level: 'ok' as InsightLevel, - }, - { - id: 'trackingscripts', - name: 'No unsafe JavaScript detected', - level: 'good' as InsightLevel, - }, - ], - }, - { - name: 'quality', - scoreValue: 60, - scoreLevel: SCORE_LEVELS.FAIR, - items: [ - { - id: 'metadatavalid', - name: 'Metadata is valid', - level: 'ok' as InsightLevel, - }, - { - id: 'code-rules', - name: 'Missing code rules', - description: 'Plugin lacks comprehensive code rules', - level: 'warning' as InsightLevel, - }, - ], - }, - ], -}; - -const mockPluginInsightsWithPoorLevel: CatalogPluginInsights = { - id: 3, - name: 'test-plugin-poor', - version: '0.8.0', - insights: [ - { - name: 'quality', - scoreValue: 35, - scoreLevel: SCORE_LEVELS.POOR, - items: [ - { - id: 'legacy-platform', - name: 'Quality issues detected', - level: 'warning' as InsightLevel, - }, - ], - }, - ], -}; - -describe('PluginInsights', () => { - it('should render plugin insights section', () => { - render(); - const insightsSection = screen.getByTestId('plugin-insights-container'); - expect(insightsSection).toBeInTheDocument(); - expect(screen.getByText('Plugin insights')).toBeInTheDocument(); - }); - - it('should render all insight categories with test ids', () => { - render(); - expect(screen.getByTestId('plugin-insight-security')).toBeInTheDocument(); - expect(screen.getByTestId('plugin-insight-quality')).toBeInTheDocument(); - }); - - it('should render category names with test ids', () => { - render(); - const securityCategory = screen.getByTestId('plugin-insight-security'); - const qualityCategory = screen.getByTestId('plugin-insight-quality'); - - expect(securityCategory).toBeInTheDocument(); - expect(securityCategory).toHaveTextContent('Security'); - expect(qualityCategory).toBeInTheDocument(); - expect(qualityCategory).toHaveTextContent('Quality'); - }); - - it('should render individual insight items with test ids', async () => { - render(); - await userEvent.click(screen.getByText('Security')); - expect(screen.getByTestId('plugin-insight-item-signature')).toBeInTheDocument(); - expect(screen.getByTestId('plugin-insight-item-trackingscripts')).toBeInTheDocument(); - await userEvent.click(screen.getByText('Quality')); - expect(screen.getByTestId('plugin-insight-item-metadatavalid')).toBeInTheDocument(); - expect(screen.getByTestId('plugin-insight-item-code-rules')).toBeInTheDocument(); - }); - - it('should display correct icons for Excellent score level', () => { - render(); - - const securityCategory = screen.getByTestId('plugin-insight-security'); - const securityIcon = securityCategory.querySelector('[data-testid="excellent-icon"]'); - expect(securityIcon).toBeInTheDocument(); - }); - - it('should display correct icons for Poor score levels', () => { - // Test Poor level - should show exclamation-triangle - render(); - const poorCategory = screen.getByTestId('plugin-insight-quality'); - const poorIcon = poorCategory.querySelector('[data-testid="poor-icon"]'); - expect(poorIcon).toBeInTheDocument(); - }); - - it('should handle multiple items with different insight levels', async () => { - const multiLevelInsights: CatalogPluginInsights = { - id: 5, - name: 'multi-level-plugin', - version: '2.0.0', - insights: [ - { - name: 'quality', - scoreValue: 75, - scoreLevel: SCORE_LEVELS.GOOD, - items: [ - { - id: 'code-rules', - name: 'Info level item', - level: 'info' as InsightLevel, - }, - { - id: 'sdk-usage', - name: 'OK level item', - level: 'ok' as InsightLevel, - }, - { - id: 'jsMap', - name: 'Good level item', - level: 'good' as InsightLevel, - }, - { - id: 'gosec', - name: 'Warning level item', - level: 'warning' as InsightLevel, - }, - { - id: 'legacy-builder', - name: 'Danger level item', - level: 'danger' as InsightLevel, - }, - ], - }, - ], - }; - render(); - await userEvent.click(screen.getByText('Quality')); - expect(screen.getByText('Info level item')).toBeInTheDocument(); - expect(screen.getByText('OK level item')).toBeInTheDocument(); - expect(screen.getByText('Good level item')).toBeInTheDocument(); - expect(screen.getByText('Warning level item')).toBeInTheDocument(); - expect(screen.getByText('Danger level item')).toBeInTheDocument(); - }); -}); diff --git a/public/app/features/plugins/admin/components/PluginInsights.tsx b/public/app/features/plugins/admin/components/PluginInsights.tsx deleted file mode 100644 index 805bcf926bf..00000000000 --- a/public/app/features/plugins/admin/components/PluginInsights.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { css } from '@emotion/css'; -import { capitalize } from 'lodash'; -import { useState } from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { Trans } from '@grafana/i18n'; -import { reportInteraction } from '@grafana/runtime'; -import { Stack, Text, TextLink, CollapsableSection, Tooltip, Icon, useStyles2, useTheme2 } from '@grafana/ui'; - -import { CatalogPluginInsights } from '../types'; - -type Props = { pluginInsights: CatalogPluginInsights | undefined }; - -const PLUGINS_INSIGHTS_OPENED_EVENT_NAME = 'plugins_insights_opened'; - -export function PluginInsights(props: Props): React.ReactElement | null { - const { pluginInsights } = props; - const styles = useStyles2(getStyles); - const theme = useTheme2(); - const [openInsights, setOpenInsights] = useState>({}); - - const handleInsightToggle = (insightName: string, isOpen: boolean) => { - if (isOpen) { - reportInteraction(PLUGINS_INSIGHTS_OPENED_EVENT_NAME, { insight: insightName }); - } - setOpenInsights((prev) => ({ ...prev, [insightName]: isOpen })); - }; - - const tooltipInfo = ( - - - - - - All relevant signals are present and verified - - - - - - - - One or more signals are missing or need attention - - - -
- - - Do you find Plugin Insights usefull? Please share your feedback{' '} - - here - - . - - -
- ); - - return ( - <> - - - - Plugin insights - - - - - - {pluginInsights?.insights.map((insightItem, index) => { - return ( - - handleInsightToggle(insightItem.name, isOpen)} - label={ - - {insightItem.scoreLevel === 'Excellent' ? ( - - ) : ( - - )} - - {capitalize(insightItem.name)} - - - } - contentClassName={styles.pluginInsightsItems} - > - - {insightItem.items.map((item, idx) => ( - - - {item.level === 'good' ? ( - - ) : ( - - )} - - - {item.name} - - - ))} - - - - ); - })} - - - ); -} - -export const getStyles = (theme: GrafanaTheme2) => { - return { - pluginVersionDetails: css({ wordBreak: 'break-word' }), - pluginInsightsItems: css({ marginLeft: '26px', paddingTop: '0 !important' }), - pluginInsightsTooltipSeparator: css({ - border: 'none', - borderTop: `1px solid ${theme.colors.border.medium}`, - margin: `${theme.spacing(1)} 0`, - }), - }; -}; diff --git a/public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts b/public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts index 3625b687f7b..9ced4f20a84 100644 --- a/public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts +++ b/public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts @@ -34,7 +34,6 @@ export default { updatedAt: '2021-08-25T15:03:49.000Z', version: '4.2.2', error: undefined, - insights: { id: 1, name: 'alexanderzobnin-zabbix-app', version: '4.2.2', insights: [] }, details: { grafanaDependency: '>=8.0.0', pluginDependencies: [], @@ -382,7 +381,6 @@ export const datasourcePlugin = { angularDetected: false, isFullyInstalled: true, latestVersion: '1.20.0', - insights: { id: 2, name: 'grafana-redshift-datasource', version: '1.20.0', insights: [] }, details: { grafanaDependency: '>=8.0.0', pluginDependencies: [], diff --git a/public/app/features/plugins/admin/mocks/mockHelpers.ts b/public/app/features/plugins/admin/mocks/mockHelpers.ts index d6e04186f77..6034e8860e9 100644 --- a/public/app/features/plugins/admin/mocks/mockHelpers.ts +++ b/public/app/features/plugins/admin/mocks/mockHelpers.ts @@ -31,9 +31,6 @@ export const getPluginsStateMock = (plugins: CatalogPlugin[] = []): ReducerState 'plugins/fetchDetails': { status: RequestStatus.Fulfilled, }, - 'plugins/fetchPluginInsights': { - status: RequestStatus.Fulfilled, - }, }, // Backward compatibility plugins: [], @@ -78,11 +75,6 @@ export const mockPluginApis = ({ return Promise.resolve({ items: versions }); } - // Mock plugin insights - return empty insights to avoid API call errors - if (path.includes('/insights')) { - return Promise.resolve({ id: 1, name: '', version: '', insights: [] }); - } - // Mock local plugin settings (installed) if necessary if (local && path === `${API_ROOT}/${local.id}/settings`) { return Promise.resolve(local); diff --git a/public/app/features/plugins/admin/state/actions.ts b/public/app/features/plugins/admin/state/actions.ts index e9cf2d9d40d..6be68111dd5 100644 --- a/public/app/features/plugins/admin/state/actions.ts +++ b/public/app/features/plugins/admin/state/actions.ts @@ -13,7 +13,6 @@ import { getPluginErrors, getLocalPlugins, getPluginDetails, - getPluginInsights, installPlugin, uninstallPlugin, getInstancePlugins, @@ -166,22 +165,6 @@ export const fetchDetails = createAsyncThunk, stri } ); -export const fetchPluginInsights = createAsyncThunk, { id: string; version?: string }>( - `${STATE_PREFIX}/fetchPluginInsights`, - async ({ id, version }, thunkApi) => { - try { - const insights = await getPluginInsights(id, version); - - return { - id, - changes: { insights }, - }; - } catch (e) { - return thunkApi.rejectWithValue('Unknown error.'); - } - } -); - export const addPlugins = createAction(`${STATE_PREFIX}/addPlugins`); // 1. gets remote equivalents from the store (if there are any) @@ -282,8 +265,7 @@ export const panelPluginLoaded = createAction(`${STATE_PREFIX}/pane // TODO export const loadPanelPlugin = (id: string): ThunkResult> => { return async (dispatch, getStore) => { - const state = getStore(); - let plugin = state.plugins.panels[id]; + let plugin = getStore().plugins.panels[id]; if (!plugin) { plugin = await importPanelPlugin(id); diff --git a/public/app/features/plugins/admin/state/hooks.ts b/public/app/features/plugins/admin/state/hooks.ts index 6eb47d7e1aa..2185ec99465 100644 --- a/public/app/features/plugins/admin/state/hooks.ts +++ b/public/app/features/plugins/admin/state/hooks.ts @@ -6,16 +6,7 @@ import { useDispatch, useSelector } from 'app/types/store'; import { sortPlugins, Sorters, isPluginUpdatable } from '../helpers'; import { CatalogPlugin, PluginStatus } from '../types'; -import { - fetchAll, - fetchDetails, - fetchRemotePlugins, - install, - uninstall, - fetchAllLocal, - unsetInstall, - fetchPluginInsights, -} from './actions'; +import { fetchAll, fetchDetails, fetchRemotePlugins, install, uninstall, fetchAllLocal, unsetInstall } from './actions'; import { selectPlugins, selectById, @@ -53,18 +44,13 @@ export const useGetUpdatable = () => { }; }; -export const useGetSingle = (id: string, version?: string): CatalogPlugin | undefined => { +export const useGetSingle = (id: string): CatalogPlugin | undefined => { useFetchAll(); useFetchDetails(id); return useSelector((state) => selectById(state, id)); }; -export const useGetPluginInsights = (id: string, version: string | undefined): CatalogPlugin | undefined => { - useFetchPluginInsights(id, version); - return useSelector((state) => selectById(state, id)); -}; - export const useGetSingleLocalWithoutDetails = (id: string): CatalogPlugin | undefined => { useFetchAllLocal(); return useSelector((state) => selectById(state, id)); @@ -167,17 +153,6 @@ export const useFetchDetails = (id: string) => { }, [plugin]); // eslint-disable-line }; -export const useFetchPluginInsights = (id: string, version: string | undefined) => { - const dispatch = useDispatch(); - const plugin = useSelector((state) => selectById(state, id)); - const isNotFetching = !useSelector(selectIsRequestPending(fetchPluginInsights.typePrefix)); - const shouldFetch = isNotFetching && plugin && !plugin.insights && version; - - useEffect(() => { - shouldFetch && dispatch(fetchPluginInsights({ id, version })); - }, [plugin, version]); // eslint-disable-line -}; - export const useFetchDetailsLazy = () => { const dispatch = useDispatch(); diff --git a/public/app/features/plugins/admin/state/reducer.ts b/public/app/features/plugins/admin/state/reducer.ts index e3d5bec5427..f2414a31405 100644 --- a/public/app/features/plugins/admin/state/reducer.ts +++ b/public/app/features/plugins/admin/state/reducer.ts @@ -7,7 +7,6 @@ import { CatalogPlugin, ReducerState, RequestStatus } from '../types'; import { fetchDetails, - fetchPluginInsights, install, uninstall, loadPluginDashboards, @@ -64,10 +63,6 @@ const slice = createSlice({ .addCase(fetchDetails.fulfilled, (state, action) => { pluginsAdapter.updateOne(state.items, action.payload); }) - // Fetch Plugin Insights - .addCase(fetchPluginInsights.fulfilled, (state, action) => { - pluginsAdapter.updateOne(state.items, action.payload); - }) // Install .addCase(install.fulfilled, (state, action) => { pluginsAdapter.updateOne(state.items, action.payload); diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts index df4114101b4..3cc66bba0b9 100644 --- a/public/app/features/plugins/admin/types.ts +++ b/public/app/features/plugins/admin/types.ts @@ -55,7 +55,6 @@ export interface CatalogPlugin extends WithAccessControlMetadata { updatedAt: string; installedVersion?: string; details?: CatalogPluginDetails; - insights?: CatalogPluginInsights; error?: PluginErrorCode; angularDetected?: boolean; // instance plugins may not be fully installed, which means a new instance @@ -91,54 +90,6 @@ export interface CatalogPluginDetails { screenshots?: Screenshots[] | null; } -export type InsightLevel = 'ok' | 'warning' | 'danger' | 'good' | 'info'; - -export const SCORE_LEVELS = { - EXCELLENT: 'Excellent', - GOOD: 'Good', - FAIR: 'Fair', - POOR: 'Poor', - CRITICAL: 'Critical', -} as const; - -export type ScoreLevel = (typeof SCORE_LEVELS)[keyof typeof SCORE_LEVELS]; - -export const INSIGHT_CATEGORIES = { - SECURITY: 'security', - QUALITY: 'quality', - PERFORMANCE: 'performance', -} as const; - -export const INSIGHT_LEVELS = { - GOOD: 'good', - OK: 'ok', - WARNING: 'warning', - DANGER: 'danger', - INFO: 'info', -} as const; - -export interface InsightItem { - id: string; - name: string; - description?: string; - level: InsightLevel; - link?: string; -} - -export interface InsightCategory { - name: string; - items: InsightItem[]; - scoreValue: number; - scoreLevel: ScoreLevel; -} - -export interface CatalogPluginInsights { - id: number; - name: string; - version: string; - insights: InsightCategory[]; -} - export interface CatalogPluginInfo { logos: { large: string; small: string }; keywords: string[]; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 2e86a2a42d3..6949fa04bb3 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -11390,12 +11390,6 @@ "latestReleaseDate": "Latest release date:", "latestVersion": "Latest Version", "license": "License", - "moreDetails": "Do you find Plugin Insights usefull? Please share your feedback <2>here.", - "pluginInsights": { - "header": "Plugin insights" - }, - "pluginInsightsSuccessTooltip": "All relevant signals are present and verified", - "pluginInsightsWarningTooltip": "One or more signals are missing or need attention", "raiseAnIssue": "Raise an issue", "reportAbuse": "Report a concern", "reportAbuseTooltip": "Report issues related to malicious or harmful plugins directly to Grafana Labs.", From 26ce2c09d78706e4235cc378d67da9f99c6ba99d Mon Sep 17 00:00:00 2001 From: Denis Vodopianov Date: Thu, 18 Dec 2025 19:10:30 +0100 Subject: [PATCH 236/297] chore: a drop-in replacement for FeatureToggles.IsEnabledGlobally in app settings (#113449) --- pkg/registry/apps/apps.go | 2 +- pkg/setting/setting.go | 5 +++++ pkg/setting/startup_setting.go | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 pkg/setting/startup_setting.go diff --git a/pkg/registry/apps/apps.go b/pkg/registry/apps/apps.go index a1ec8aafd65..77c96fd437c 100644 --- a/pkg/registry/apps/apps.go +++ b/pkg/registry/apps/apps.go @@ -76,7 +76,7 @@ func ProvideAppInstallers( if features.IsEnabledGlobally(featuremgmt.FlagKubernetesLogsDrilldown) { installers = append(installers, logsdrilldownAppInstaller) } - //nolint:staticcheck + //nolint:staticcheck // not yet migrated to OpenFeature if features.IsEnabledGlobally(featuremgmt.FlagKubernetesAnnotations) { installers = append(installers, annotationAppInstaller) } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 1af02aa0d3a..3d202b20cec 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -510,6 +510,9 @@ type Cfg struct { // Query history QueryHistoryEnabled bool + // StartupSettings settings + StartupSettings StartupSettings + // Open feature settings OpenFeature OpenFeatureSettings @@ -1471,6 +1474,8 @@ func (cfg *Cfg) parseINIFile(iniFile *ini.File) error { // unified storage config cfg.setUnifiedStorageConfig() + // app platform config + cfg.readStartupSettingsSection() return nil } diff --git a/pkg/setting/startup_setting.go b/pkg/setting/startup_setting.go new file mode 100644 index 00000000000..8b2c2fa6362 --- /dev/null +++ b/pkg/setting/startup_setting.go @@ -0,0 +1,14 @@ +package setting + +type StartupSettings struct { + KubernetesAnnotationsAppEnabled bool +} + +func (cfg *Cfg) readStartupSettingsSection() { + settings := StartupSettings{} + + startupSettingsSection := cfg.Raw.Section("startup_settings") + settings.KubernetesAnnotationsAppEnabled = startupSettingsSection.Key("annotations_app_enabled").MustBool(false) + + cfg.StartupSettings = settings +} From 18501633467c50ec9afb4b224eb803fdf91d38b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Fern=C3=A1ndez?= Date: Thu, 18 Dec 2025 19:47:04 +0100 Subject: [PATCH 237/297] Rudderstack: Add new config option for rudderstack v3 url (#115374) --- conf/defaults.ini | 3 +++ conf/sample.ini | 3 +++ .../setup-grafana/configure-grafana/_index.md | 6 ++++++ packages/grafana-data/src/types/config.ts | 1 + packages/grafana-runtime/src/config.ts | 1 + pkg/api/dtos/frontend_settings.go | 1 + pkg/api/frontendsettings.go | 1 + pkg/services/frontend/frontend_settings.go | 1 + pkg/services/frontend/index.go | 1 + pkg/setting/setting.go | 2 ++ public/app/core/services/echo/init.ts | 16 +++++++--------- 11 files changed, 27 insertions(+), 9 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index de83393e43d..8ee03e6a34c 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -335,6 +335,9 @@ rudderstack_data_plane_url = # Rudderstack SDK url, optional, only valid if rudderstack_write_key and rudderstack_data_plane_url is also set rudderstack_sdk_url = +# Rudderstack v3 SDK, optional, defaults to false. If set, Rudderstack v3 SDK will be used instead of v1 +rudderstack_v3_sdk_url = + # Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config rudderstack_config_url = diff --git a/conf/sample.ini b/conf/sample.ini index d1d50f0a72a..0bb5b82fdc9 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -322,6 +322,9 @@ # Rudderstack SDK url, optional, only valid if rudderstack_write_key and rudderstack_data_plane_url is also set ;rudderstack_sdk_url = +# Rudderstack v3 SDK, optional, defaults to false. If set, Rudderstack v3 SDK will be used instead of v1 +;rudderstack_v3_sdk_url = + # Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config ;rudderstack_config_url = diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index a82ca8f91dd..05d9cb66228 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -642,6 +642,12 @@ You must also provide the `rudderstack_write_key` to enable this feature. Optional. If tracking with RudderStack is enabled, you can provide a custom URL to load the RudderStack SDK. +#### `rudderstack_v3_sdk_url` + +Optional. +This is mirroring the old configuration option, which will be deprecated. +If `rudderstack_sdk_url` and `rudderstack_v3_sdk_url` are both set, the feature toggle `rudderstackUpgrade` will control which one is loaded. + #### `rudderstack_config_url` Optional. diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index f519b1d56b4..b2d3c16a3b1 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -289,6 +289,7 @@ export interface GrafanaConfig { rudderstackWriteKey: string; rudderstackDataPlaneUrl: string; rudderstackSdkUrl: string; + rudderstackV3SdkUrl: string; rudderstackConfigUrl: string; rudderstackIntegrationsUrl: string; applicationInsightsConnectionString: string; diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 17fbbef447a..18cce14f236 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -224,6 +224,7 @@ export class GrafanaBootConfig { rudderstackWriteKey?: string; rudderstackDataPlaneUrl?: string; rudderstackSdkUrl?: string; + rudderstackV3SdkUrl?: string; rudderstackConfigUrl?: string; rudderstackIntegrationsUrl?: string; analyticsConsoleReporting = false; diff --git a/pkg/api/dtos/frontend_settings.go b/pkg/api/dtos/frontend_settings.go index 218817c6801..d140c328242 100644 --- a/pkg/api/dtos/frontend_settings.go +++ b/pkg/api/dtos/frontend_settings.go @@ -200,6 +200,7 @@ type FrontendSettingsDTO struct { RudderstackWriteKey string `json:"rudderstackWriteKey"` RudderstackDataPlaneUrl string `json:"rudderstackDataPlaneUrl"` RudderstackSdkUrl string `json:"rudderstackSdkUrl"` + RudderstackV3SdkUrl string `json:"rudderstackV3SdkUrl"` RudderstackConfigUrl string `json:"rudderstackConfigUrl"` RudderstackIntegrationsUrl string `json:"rudderstackIntegrationsUrl"` diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index c105b5ee829..b57262087e5 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -229,6 +229,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro RudderstackWriteKey: hs.Cfg.RudderstackWriteKey, RudderstackDataPlaneUrl: hs.Cfg.RudderstackDataPlaneURL, RudderstackSdkUrl: hs.Cfg.RudderstackSDKURL, + RudderstackV3SdkUrl: hs.Cfg.RudderstackV3SDKURL, RudderstackConfigUrl: hs.Cfg.RudderstackConfigURL, RudderstackIntegrationsUrl: hs.Cfg.RudderstackIntegrationsURL, AnalyticsConsoleReporting: hs.Cfg.FrontendAnalyticsConsoleReporting, diff --git a/pkg/services/frontend/frontend_settings.go b/pkg/services/frontend/frontend_settings.go index f6cf9855b2d..d9476c8ff40 100644 --- a/pkg/services/frontend/frontend_settings.go +++ b/pkg/services/frontend/frontend_settings.go @@ -32,6 +32,7 @@ type FSFrontendSettings struct { RudderstackWriteKey string `json:"rudderstackWriteKey,omitempty"` RudderstackDataPlaneUrl string `json:"rudderstackDataPlaneUrl,omitempty"` RudderstackSdkUrl string `json:"rudderstackSdkUrl,omitempty"` + RudderstackV3SdkUrl string `json:"rudderstackV3SdkUrl,omitempty"` RudderstackConfigUrl string `json:"rudderstackConfigUrl,omitempty"` RudderstackIntegrationsUrl string `json:"rudderstackIntegrationsUrl,omitempty"` diff --git a/pkg/services/frontend/index.go b/pkg/services/frontend/index.go index e87ca894d20..25704f1ab6d 100644 --- a/pkg/services/frontend/index.go +++ b/pkg/services/frontend/index.go @@ -94,6 +94,7 @@ func NewIndexProvider(cfg *setting.Cfg, assetsManifest dtos.EntryPointAssets, li RudderstackDataPlaneUrl: cfg.RudderstackDataPlaneURL, RudderstackIntegrationsUrl: cfg.RudderstackIntegrationsURL, RudderstackSdkUrl: cfg.RudderstackSDKURL, + RudderstackV3SdkUrl: cfg.RudderstackV3SDKURL, RudderstackWriteKey: cfg.RudderstackWriteKey, TrustedTypesDefaultPolicyEnabled: (cfg.CSPEnabled && strings.Contains(cfg.CSPTemplate, "require-trusted-types-for")) || (cfg.CSPReportOnlyEnabled && strings.Contains(cfg.CSPReportOnlyTemplate, "require-trusted-types-for")), VerifyEmailEnabled: cfg.VerifyEmailEnabled, diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 3d202b20cec..62b9332581f 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -414,6 +414,7 @@ type Cfg struct { RudderstackDataPlaneURL string RudderstackWriteKey string RudderstackSDKURL string + RudderstackV3SDKURL string RudderstackConfigURL string RudderstackIntegrationsURL string IntercomSecret string @@ -1284,6 +1285,7 @@ func (cfg *Cfg) parseINIFile(iniFile *ini.File) error { cfg.RudderstackWriteKey = analytics.Key("rudderstack_write_key").String() cfg.RudderstackDataPlaneURL = analytics.Key("rudderstack_data_plane_url").String() cfg.RudderstackSDKURL = analytics.Key("rudderstack_sdk_url").String() + cfg.RudderstackV3SDKURL = analytics.Key("rudderstack_v3_sdk_url").String() cfg.RudderstackConfigURL = analytics.Key("rudderstack_config_url").String() cfg.RudderstackIntegrationsURL = analytics.Key("rudderstack_integrations_url").String() cfg.IntercomSecret = analytics.Key("intercom_secret").String() diff --git a/public/app/core/services/echo/init.ts b/public/app/core/services/echo/init.ts index 5e49e72422a..9fce1a539ef 100644 --- a/public/app/core/services/echo/init.ts +++ b/public/app/core/services/echo/init.ts @@ -146,17 +146,15 @@ async function initRudderstackBackend() { return; } - // this will need to be updated when rudderstackSdkV3Url is added - // Desired logic: if only one of the sdk urls is provided, use respective code + // Logic: if only one of the sdk urls is provided, use respective code // otherwise defer to the feature toggle. - const fakeConfigRudderstackSdkV3Url: string | undefined = undefined; const hasOldSdkUrl = Boolean(config.rudderstackSdkUrl); - const hasNewSdkUrl = Boolean(fakeConfigRudderstackSdkV3Url); - const onlyOneConfigURLSet = hasOldSdkUrl !== hasNewSdkUrl; - const useNewRudderstack = onlyOneConfigURLSet ? hasNewSdkUrl : config.featureToggles.rudderstackUpgrade; + const hasNewSdkUrl = Boolean(config.rudderstackV3SdkUrl); + const onlyOneSdkUrlSet = hasOldSdkUrl !== hasNewSdkUrl; + const useNewRudderstack = onlyOneSdkUrlSet ? hasNewSdkUrl : config.featureToggles.rudderstackUpgrade; - const configUrl = useNewRudderstack ? fakeConfigRudderstackSdkV3Url : config.rudderstackSdkUrl; + const sdkUrl = useNewRudderstack ? config.rudderstackV3SdkUrl : config.rudderstackSdkUrl; const modulePromise = useNewRudderstack ? import('./backends/analytics/RudderstackV3Backend') @@ -168,8 +166,8 @@ async function initRudderstackBackend() { writeKey: config.rudderstackWriteKey, dataPlaneUrl: config.rudderstackDataPlaneUrl, user: contextSrv.user, - sdkUrl: config.rudderstackSdkUrl, - configUrl: configUrl, + sdkUrl, + configUrl: config.rudderstackConfigUrl, integrationsUrl: config.rudderstackIntegrationsUrl, buildInfo: config.buildInfo, }) From 05fd304dbd2be526ac96c29349f361d600144d29 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:58:21 -0700 Subject: [PATCH 238/297] Dashboards: AdHoc and GroupBy wrapper (#115124) * wip; DrilldownControls * use wrapper so that drilldown controls wrap inline * keep labels on top when input expands vertically * add clear all button * add collapsible prop * i18n * Increase maxWidth for adhoc * bump scenes for testing * fix * remove clear all button * use new feature toggle; pass collapsible in v2 * update variable controls to use new feature flag * cleanup * wip (#115441) * wip * fix * update wrapping on smaller screens --------- Co-authored-by: Haris Rozajac * Filter out variables that are not in inControlsMenu * filter out inControlsMenu vars, not hidden ones * canary scenes * fix * cleanup * canary scenes * pass wideInput to groupby based on ff * update var name and bump scenes * bump scenes * yarn lock --------- Co-authored-by: Victor Marin --- package.json | 4 +- .../src/types/featureToggles.gen.ts | 4 + pkg/services/featuremgmt/registry.go | 7 ++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 + pkg/services/featuremgmt/toggles_gen.json | 13 +++ .../scene/DashboardControls.tsx | 97 +++++++++++++++++++ .../scene/DrilldownControls.tsx | 77 +++++++++++++++ .../scene/VariableControls.tsx | 23 ++++- .../transformSaveModelSchemaV2ToScene.ts | 2 + .../dashboard-scene/utils/variables.ts | 2 + yarn.lock | 22 ++--- 12 files changed, 240 insertions(+), 16 deletions(-) create mode 100644 public/app/features/dashboard-scene/scene/DrilldownControls.tsx diff --git a/package.json b/package.json index 37dc6ff745c..73dec9dbc90 100644 --- a/package.json +++ b/package.json @@ -295,8 +295,8 @@ "@grafana/plugin-ui": "^0.11.1", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^6.51.0", - "@grafana/scenes-react": "^6.51.0", + "@grafana/scenes": "6.52.0", + "@grafana/scenes-react": "6.52.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index b91161d966c..e32ce90bcc5 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -499,6 +499,10 @@ export interface FeatureToggles { */ newDashboardWithFiltersAndGroupBy?: boolean; /** + * Wraps the ad hoc and group by variables in a single wrapper, with all other variables below it + */ + dashboardAdHocAndGroupByWrapper?: boolean; + /** * Updates CloudWatch label parsing to be more accurate * @default true */ diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index d8a46601ad6..bd2707f3356 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -820,6 +820,13 @@ var ( Owner: grafanaDashboardsSquad, HideFromDocs: true, }, + { + Name: "dashboardAdHocAndGroupByWrapper", + Description: "Wraps the ad hoc and group by variables in a single wrapper, with all other variables below it", + Stage: FeatureStageExperimental, + Owner: grafanaDashboardsSquad, + HideFromDocs: true, + }, { Name: "cloudWatchNewLabelParsing", Description: "Updates CloudWatch label parsing to be more accurate", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 3fbc019bff1..095350eb0c8 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -113,6 +113,7 @@ scopeFilters,experimental,@grafana/dashboards-squad,false,false,false oauthRequireSubClaim,experimental,@grafana/identity-access-team,false,false,false refreshTokenRequired,experimental,@grafana/identity-access-team,false,false,false newDashboardWithFiltersAndGroupBy,experimental,@grafana/dashboards-squad,false,false,false +dashboardAdHocAndGroupByWrapper,experimental,@grafana/dashboards-squad,false,false,false cloudWatchNewLabelParsing,GA,@grafana/aws-datasources,false,false,false disableNumericMetricsSortingInExpressions,experimental,@grafana/oss-big-tent,false,true,false grafanaManagedRecordingRules,experimental,@grafana/alerting-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 020e0bce893..fd77326e8b0 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -339,6 +339,10 @@ const ( // Enables filters and group by variables on all new dashboards. Variables are added only if default data source supports filtering. FlagNewDashboardWithFiltersAndGroupBy = "newDashboardWithFiltersAndGroupBy" + // FlagDashboardAdHocAndGroupByWrapper + // Wraps the ad hoc and group by variables in a single wrapper, with all other variables below it + FlagDashboardAdHocAndGroupByWrapper = "dashboardAdHocAndGroupByWrapper" + // FlagCloudWatchNewLabelParsing // Updates CloudWatch label parsing to be more accurate FlagCloudWatchNewLabelParsing = "cloudWatchNewLabelParsing" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 866f8a78881..0de187cce52 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -922,6 +922,19 @@ "frontend": true } }, + { + "metadata": { + "name": "dashboardAdHocAndGroupByWrapper", + "resourceVersion": "1765841806645", + "creationTimestamp": "2025-12-15T23:36:46Z" + }, + "spec": { + "description": "Wraps the ad hoc and group by variables in a single wrapper, with all other variables below it", + "stage": "experimental", + "codeowner": "@grafana/dashboards-squad", + "hideFromDocs": true + } + }, { "metadata": { "name": "dashboardDisableSchemaValidationV1", diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.tsx index 32f7d5289f8..a44fd918393 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControls.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControls.tsx @@ -16,6 +16,7 @@ import { SceneObjectUrlSyncConfig, SceneObjectUrlValues, CancelActivationHandler, + sceneUtils, } from '@grafana/scenes'; import { Box, Button, useStyles2 } from '@grafana/ui'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; @@ -27,6 +28,7 @@ import { getDashboardSceneFor } from '../utils/utils'; import { DashboardDataLayerControls } from './DashboardDataLayerControls'; import { DashboardLinksControls } from './DashboardLinksControls'; import { DashboardScene } from './DashboardScene'; +import { DrilldownControls } from './DrilldownControls'; import { VariableControls } from './VariableControls'; import { DashboardControlsButton } from './dashboard-controls-menu/DashboardControlsMenuButton'; import { hasDashboardControls, useHasDashboardControls } from './dashboard-controls-menu/utils'; @@ -151,11 +153,63 @@ function DashboardControlsRenderer({ model }: SceneComponentProps v.state.hide !== VariableHide.inControlsMenu); + const adHocVar = visibleVariables.find((v) => sceneUtils.isAdHocVariable(v)); + const groupByVar = visibleVariables.find((v) => sceneUtils.isGroupByVariable(v)); + const useUnifiedDrilldownUI = config.featureToggles.dashboardAdHocAndGroupByWrapper && adHocVar && groupByVar; + if (!model.hasControls()) { // To still have spacing when no controls are rendered return {renderHiddenVariables(dashboard)}; } + // When dashboardAdHocAndGroupByWrapper is enabled, use the new layout with topRow + if (useUnifiedDrilldownUI) { + return ( +
+
+ {config.featureToggles.scopeFilters && !editPanel && ( + + )} + {!hideVariableControls && ( +
+ +
+ )} +
+ {!hideTimeControls && ( +
+ + +
+ )} + {config.featureToggles.dashboardNewLayouts && ( +
+ +
+ )} +
+
+ {!hideVariableControls && ( + <> + + + + )} + {!hideLinksControls && !editPanel && } + {!hideDashboardControls && hasDashboardControls && } + {editPanel && } + {showDebugger && } +
+ ); + } + + // Original layout when feature toggle is off return (
+
+ +
+
+ +
+
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => ({ + drilldownRow: css({ + display: 'flex', + flexWrap: 'nowrap', + [theme.breakpoints.down('xl')]: { + flexWrap: 'wrap', + }, + gap: theme.spacing(1), + width: '100%', + }), + adHocContainer: css({ + flex: '7 1 0%', // 70% of available space + minWidth: 0, + [theme.breakpoints.down('xl')]: { + // Force full width, causing groupBy to wrap + flex: '1 1 100%', + }, + display: 'flex', + // Make the wrapper and its children take full width + '& > div': { + alignItems: 'flex-start', + width: '100%', + flex: 1, + }, + }), + groupByContainer: css({ + flex: '3 1 0%', // 30% of available space + minWidth: 0, + display: 'flex', + // Make the wrapper and its children take full width + '& > div': { + alignItems: 'flex-start', + width: '100%', + flex: 1, + }, + [theme.breakpoints.down('sm')]: { + minWidth: '200px', + }, + }), + clearAllButton: css({ + alignSelf: 'flex-start', + fontSize: theme.typography.bodySmall.fontSize, + padding: theme.spacing(0.5, 0.5), + marginBottom: theme.spacing(1), + }), +}); diff --git a/public/app/features/dashboard-scene/scene/VariableControls.tsx b/public/app/features/dashboard-scene/scene/VariableControls.tsx index 03d23712c7b..73dd2614472 100644 --- a/public/app/features/dashboard-scene/scene/VariableControls.tsx +++ b/public/app/features/dashboard-scene/scene/VariableControls.tsx @@ -20,13 +20,30 @@ import { AddVariableButton } from './VariableControlsAddButton'; export function VariableControls({ dashboard }: { dashboard: DashboardScene }) { const { variables } = sceneGraph.getVariables(dashboard)!.useState(); + // Get visible variables for drilldown layout + const visibleVariables = variables.filter((v) => v.state.hide !== VariableHide.inControlsMenu); + + const adHocVar = visibleVariables.find((v) => sceneUtils.isAdHocVariable(v)); + const groupByVar = visibleVariables.find((v) => sceneUtils.isGroupByVariable(v)); + + const hasDrilldownControls = config.featureToggles.dashboardAdHocAndGroupByWrapper && adHocVar && groupByVar; + + const restVariables = visibleVariables.filter( + (v) => v.state.name !== adHocVar?.state.name && v.state.name !== groupByVar?.state.name + ); + + // Variables to render (exclude adhoc/groupby when drilldown controls are shown in top row) + const variablesToRender = hasDrilldownControls + ? restVariables.filter((v) => v.state.hide !== VariableHide.inControlsMenu) + : variables.filter((v) => v.state.hide !== VariableHide.inControlsMenu); + return ( <> - {variables - .filter((v) => v.state.hide !== VariableHide.inControlsMenu) - .map((variable) => ( + {variablesToRender.length > 0 && + variablesToRender.map((variable) => ( ))} + {config.featureToggles.dashboardNewLayouts ? : null} ); diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts index ff3927d3814..f343cbce00d 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelSchemaV2ToScene.ts @@ -336,6 +336,7 @@ function createSceneVariableFromVariableModel(variable: TypedVariableModelV2): S supportsMultiValueOperators: Boolean( getDataSourceSrv().getInstanceSettings({ type: ds?.type })?.meta.multiValueFilterOperators ), + collapsible: config.featureToggles.dashboardAdHocAndGroupByWrapper, }; if (variable.spec.allowCustomValue !== undefined) { adhocVariableState.allowCustomValue = variable.spec.allowCustomValue; @@ -460,6 +461,7 @@ function createSceneVariableFromVariableModel(variable: TypedVariableModelV2): S skipUrlSync: variable.spec.skipUrlSync, isMulti: variable.spec.multi, hide: transformVariableHideToEnumV1(variable.spec.hide), + wideInput: config.featureToggles.dashboardAdHocAndGroupByWrapper, drilldownRecommendationsEnabled: config.featureToggles.drilldownRecommendations, // @ts-expect-error defaultOptions: variable.options, diff --git a/public/app/features/dashboard-scene/utils/variables.ts b/public/app/features/dashboard-scene/utils/variables.ts index 2f262781b00..cd09e094845 100644 --- a/public/app/features/dashboard-scene/utils/variables.ts +++ b/public/app/features/dashboard-scene/utils/variables.ts @@ -164,6 +164,7 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode useQueriesAsFilterForOptions: true, drilldownRecommendationsEnabled: config.featureToggles.drilldownRecommendations, layout: config.featureToggles.newFiltersUI ? 'combobox' : undefined, + collapsible: config.featureToggles.dashboardAdHocAndGroupByWrapper, supportsMultiValueOperators: Boolean( getDataSourceSrv().getInstanceSettings({ type: variable.datasource?.type })?.meta.multiValueFilterOperators ), @@ -288,6 +289,7 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode text: variable.current?.text || [], skipUrlSync: variable.skipUrlSync, hide: variable.hide, + wideInput: config.featureToggles.dashboardAdHocAndGroupByWrapper, // @ts-expect-error defaultOptions: variable.options, defaultValue: variable.defaultValue, diff --git a/yarn.lock b/yarn.lock index a60a9c08912..64561ce4dca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3604,11 +3604,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:^6.51.0": - version: 6.51.0 - resolution: "@grafana/scenes-react@npm:6.51.0" +"@grafana/scenes-react@npm:6.52.0": + version: 6.52.0 + resolution: "@grafana/scenes-react@npm:6.52.0" dependencies: - "@grafana/scenes": "npm:6.51.0" + "@grafana/scenes": "npm:6.52.0" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3620,7 +3620,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/14acdfe5220e67e7450780320b779e2e4a255995d55f0c82eb0d25933e72598e54826df0a8beee05591efe01a91ddab840483fea3bb828bd5925c3f0b44b8d17 + checksum: 10/7f121bcc4fd50f525c7c3457666ad3a32b04783d322d6715aedb6119538f911d0ec265c9c5b49a80478c1deb99286d36d003399a8831f76b6c483f4458b4ce8b languageName: node linkType: hard @@ -3650,9 +3650,9 @@ __metadata: languageName: node linkType: hard -"@grafana/scenes@npm:6.51.0, @grafana/scenes@npm:^6.51.0": - version: 6.51.0 - resolution: "@grafana/scenes@npm:6.51.0" +"@grafana/scenes@npm:6.52.0": + version: 6.52.0 + resolution: "@grafana/scenes@npm:6.52.0" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3672,7 +3672,7 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/4e4f43babe786ff729d58b7636182df57c58ce40c13b56036f725c070e0cf597cbe52aaa0f811184b8d42d8d1f9a32679695471d410f883051b09da44f8bf36a + checksum: 10/e52e0fb83396776c6cb79f8ac6a8aad0799eb2ccce9d0139f5734a49c3add7a1e3b97f14e0142c95b2bceee3ed8fa97b675b9b94c02382ecd683f470d06ef145 languageName: node linkType: hard @@ -19508,8 +19508,8 @@ __metadata: "@grafana/plugin-ui": "npm:^0.11.1" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:^6.51.0" - "@grafana/scenes-react": "npm:^6.51.0" + "@grafana/scenes": "npm:6.52.0" + "@grafana/scenes-react": "npm:6.52.0" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/test-utils": "workspace:*" From 39c562a911a0a7dc3663223adbcd92cc33fb18be Mon Sep 17 00:00:00 2001 From: Denis Vodopianov Date: Thu, 18 Dec 2025 22:46:32 +0100 Subject: [PATCH 239/297] Revert: chore: a drop-in replacement for FeatureToggles.IsEnabledGlobally in app settings (#115593) * Revert "chore: a drop-in replacement for FeatureToggles.IsEnabledGlobally in app settings (#113449)" This reverts commit 26ce2c09d78706e4235cc378d67da9f99c6ba99d. * Change FeatureToggles.IsEnabledGlobally deprecation message --- pkg/registry/apps/apps.go | 2 +- pkg/services/featuremgmt/models.go | 4 +--- pkg/setting/setting.go | 5 ----- pkg/setting/startup_setting.go | 14 -------------- 4 files changed, 2 insertions(+), 23 deletions(-) delete mode 100644 pkg/setting/startup_setting.go diff --git a/pkg/registry/apps/apps.go b/pkg/registry/apps/apps.go index 77c96fd437c..a1ec8aafd65 100644 --- a/pkg/registry/apps/apps.go +++ b/pkg/registry/apps/apps.go @@ -76,7 +76,7 @@ func ProvideAppInstallers( if features.IsEnabledGlobally(featuremgmt.FlagKubernetesLogsDrilldown) { installers = append(installers, logsdrilldownAppInstaller) } - //nolint:staticcheck // not yet migrated to OpenFeature + //nolint:staticcheck if features.IsEnabledGlobally(featuremgmt.FlagKubernetesAnnotations) { installers = append(installers, annotationAppInstaller) } diff --git a/pkg/services/featuremgmt/models.go b/pkg/services/featuremgmt/models.go index 304d17ee075..d59dff63c37 100644 --- a/pkg/services/featuremgmt/models.go +++ b/pkg/services/featuremgmt/models.go @@ -23,9 +23,7 @@ type FeatureToggles interface { // a full server restart for a change to take place. // // Deprecated: FeatureToggles.IsEnabledGlobally is deprecated and will be removed in a future release. - // Toggles that must be reliably evaluated at the service startup should be - // changed to settings (see setting.StartupSettings), and/or removed entirely. - // For app registration please use `grafana-apiserver.runtime_config` in settings.ini + // Toggles that must be reliably evaluated at the service startup should be changed to settings and/or removed entirely. IsEnabledGlobally(flag string) bool // Get the enabled flags -- this *may* also include disabled flags (with value false) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 62b9332581f..00bcdabd88d 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -511,9 +511,6 @@ type Cfg struct { // Query history QueryHistoryEnabled bool - // StartupSettings settings - StartupSettings StartupSettings - // Open feature settings OpenFeature OpenFeatureSettings @@ -1476,8 +1473,6 @@ func (cfg *Cfg) parseINIFile(iniFile *ini.File) error { // unified storage config cfg.setUnifiedStorageConfig() - // app platform config - cfg.readStartupSettingsSection() return nil } diff --git a/pkg/setting/startup_setting.go b/pkg/setting/startup_setting.go deleted file mode 100644 index 8b2c2fa6362..00000000000 --- a/pkg/setting/startup_setting.go +++ /dev/null @@ -1,14 +0,0 @@ -package setting - -type StartupSettings struct { - KubernetesAnnotationsAppEnabled bool -} - -func (cfg *Cfg) readStartupSettingsSection() { - settings := StartupSettings{} - - startupSettingsSection := cfg.Raw.Section("startup_settings") - settings.KubernetesAnnotationsAppEnabled = startupSettingsSection.Key("annotations_app_enabled").MustBool(false) - - cfg.StartupSettings = settings -} From 37c1e3fb02f46f315ea53b301fe089d86374419f Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:11:09 -0700 Subject: [PATCH 240/297] Dashboard Schema v1beta1 to v2alpha1: Preserve string template variable datasource references in query variables (#115516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Dashboard migration: preserve legacy string datasource references Fix v1beta1 → v2alpha1 conversion to handle legacy string datasource references in QueryVariable, AdhocVariable, and GroupByVariable. Previously, string datasource references (both template variables like "$datasource" and direct names/UIDs like "prometheus") were being dropped during conversion, causing variable chaining to break. The frontend's DatasourceSrv.getInstanceSettings() already handles string references by trying uid → name → id lookup at runtime, so we preserve the string in the uid field and let the frontend resolve it. * trigger frontend ci tests when dashboard migration code changes * v1: if string convert to DS ref * Update migration testdata to fix template variable datasource references * update --- .github/actions/change-detection/action.yml | 1 + ....mimir_rollout_debugging.v42.v0alpha1.json | 8 ++- ....mimir_rollout_debugging.v42.v2alpha1.json | 12 ++++- ...5.mimir_rollout_debugging.v42.v2beta1.json | 10 +++- .../conversion/v0alpha1_to_v1beta1.go | 53 +++++++++++++++++++ .../conversion/v1beta1_to_v2alpha1.go | 12 +++++ .../input/v15.mimir_rollout_debugging.json | 8 ++- .../v15.mimir_rollout_debugging.v42.json | 8 ++- .../v15.mimir_rollout_debugging.v15.json | 8 ++- 9 files changed, 108 insertions(+), 12 deletions(-) diff --git a/.github/actions/change-detection/action.yml b/.github/actions/change-detection/action.yml index 2c6b46606f4..863cab646b9 100644 --- a/.github/actions/change-detection/action.yml +++ b/.github/actions/change-detection/action.yml @@ -95,6 +95,7 @@ runs: - 'nx.json' - 'tsconfig.json' - '.yarn/**' + - 'apps/dashboard/pkg/migration/**' - '${{ inputs.self }}' e2e: - 'e2e/**' diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v0alpha1.json index 6c259253e55..35cc948844c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v0alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v0alpha1.json @@ -743,7 +743,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": true, "label": "cluster", @@ -764,7 +766,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": false, "label": "namespace", diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2alpha1.json index 8d340c0f65e..26945033c4c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2alpha1.json @@ -961,8 +961,12 @@ "hide": "dontHide", "refresh": "onDashboardLoad", "skipUrlSync": false, + "datasource": { + "type": "", + "uid": "$datasource" + }, "query": { - "kind": "prometheus", + "kind": "", "spec": { "__legacyStringValue": "label_values(up, job)" } @@ -988,8 +992,12 @@ "hide": "dontHide", "refresh": "onDashboardLoad", "skipUrlSync": false, + "datasource": { + "type": "", + "uid": "$datasource" + }, "query": { - "kind": "prometheus", + "kind": "", "spec": { "__legacyStringValue": "label_values(up{job=~\"$cluster\"}, instance)" } diff --git a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2beta1.json index 8070895766a..344b0ef564a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/migrated_dashboards_output/v1beta1-mig-v15.mimir_rollout_debugging.v42.v2beta1.json @@ -978,8 +978,11 @@ "skipUrlSync": false, "query": { "kind": "DataQuery", - "group": "prometheus", + "group": "", "version": "v0", + "datasource": { + "name": "$datasource" + }, "spec": { "__legacyStringValue": "label_values(up, job)" } @@ -1007,8 +1010,11 @@ "skipUrlSync": false, "query": { "kind": "DataQuery", - "group": "prometheus", + "group": "", "version": "v0", + "datasource": { + "name": "$datasource" + }, "spec": { "__legacyStringValue": "label_values(up{job=~\"$cluster\"}, instance)" } diff --git a/apps/dashboard/pkg/migration/conversion/v0alpha1_to_v1beta1.go b/apps/dashboard/pkg/migration/conversion/v0alpha1_to_v1beta1.go index 2154a26e3a2..d0679c76e63 100644 --- a/apps/dashboard/pkg/migration/conversion/v0alpha1_to_v1beta1.go +++ b/apps/dashboard/pkg/migration/conversion/v0alpha1_to_v1beta1.go @@ -2,6 +2,7 @@ package conversion import ( "context" + "strings" "k8s.io/apimachinery/pkg/conversion" "k8s.io/apiserver/pkg/endpoints/request" @@ -79,5 +80,57 @@ func ConvertDashboard_V0_to_V1beta1(in *dashv0.Dashboard, out *dashv1.Dashboard, return schemaversion.NewMigrationError(err.Error(), schemaversion.GetSchemaVersion(in.Spec.Object), schemaversion.LATEST_VERSION, "Convert_V0_to_V1") } + // Normalize template variable datasources from string to object format + // This handles legacy dashboards where query variables have datasource: "$datasource" (string) + // instead of datasource: { uid: "$datasource" } (object) + // our migration pipeline in v36 doesn't address because this was not addressed historically + // in DashboardMigrator - see public/app/features/dashboard/state/DashboardMigrator.ts#L607 + // Which means that we have schemaVersion: 42 dashboards where datasource variable references are still strings + normalizeTemplateVariableDatasources(out.Spec.Object) + return nil } + +// normalizeTemplateVariableDatasources converts template variable string datasources to object format. +// Legacy dashboards may have query variables with datasource: "$datasource" (string). +// This normalizes them to datasource: { uid: "$datasource" } for consistent V1→V2 conversion. +func normalizeTemplateVariableDatasources(dashboard map[string]interface{}) { + templating, ok := dashboard["templating"].(map[string]interface{}) + if !ok { + return + } + + list, ok := templating["list"].([]interface{}) + if !ok { + return + } + + for _, variable := range list { + varMap, ok := variable.(map[string]interface{}) + if !ok { + continue + } + + varType, _ := varMap["type"].(string) + if varType != "query" { + continue + } + + ds := varMap["datasource"] + if dsStr, ok := ds.(string); ok && isTemplateVariableRef(dsStr) { + // Convert string template variable reference to object format + varMap["datasource"] = map[string]interface{}{ + "uid": dsStr, + } + } + } +} + +// isTemplateVariableRef checks if a string is a Grafana template variable reference. +// Template variables can be in the form: $varname or ${varname} +func isTemplateVariableRef(s string) bool { + if s == "" { + return false + } + return strings.HasPrefix(s, "$") || strings.HasPrefix(s, "${") +} diff --git a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go index 47c12a6ac94..92c5d937fe7 100644 --- a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go +++ b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go @@ -1185,6 +1185,10 @@ func buildQueryVariable(ctx context.Context, varMap map[string]interface{}, comm // If no UID and no type, use default datasourceType = getDefaultDatasourceType(ctx, dsIndexProvider) } + } else if dsStr, ok := datasource.(string); ok && isTemplateVariable(dsStr) { + // Handle datasource variable reference (e.g., "$datasource") + // Only process template variables - other string values are not supported in V2 format + datasourceUID = dsStr } else { datasourceType = getDefaultDatasourceType(ctx, dsIndexProvider) } @@ -1532,6 +1536,10 @@ func buildAdhocVariable(ctx context.Context, varMap map[string]interface{}, comm // If no UID and no type, use default datasourceType = getDefaultDatasourceType(ctx, dsIndexProvider) } + } else if dsStr, ok := datasource.(string); ok && isTemplateVariable(dsStr) { + // Handle datasource variable reference (e.g., "$datasource") + // Only process template variables - other string values are not supported in V2 format + datasourceUID = dsStr } else { datasourceType = getDefaultDatasourceType(ctx, dsIndexProvider) } @@ -1709,6 +1717,10 @@ func buildGroupByVariable(ctx context.Context, varMap map[string]interface{}, co // Resolve Grafana datasource UID when type is "datasource" and UID is empty datasourceUID = resolveGrafanaDatasourceUID(datasourceType, datasourceUID) + } else if dsStr, ok := datasource.(string); ok && isTemplateVariable(dsStr) { + // Handle datasource variable reference (e.g., "$datasource") + // Only process template variables - other string values are not supported in V2 format + datasourceUID = dsStr } else { datasourceType = getDefaultDatasourceType(ctx, dsIndexProvider) } diff --git a/apps/dashboard/pkg/migration/testdata/input/v15.mimir_rollout_debugging.json b/apps/dashboard/pkg/migration/testdata/input/v15.mimir_rollout_debugging.json index dc0ca4e5845..1cc39838eab 100644 --- a/apps/dashboard/pkg/migration/testdata/input/v15.mimir_rollout_debugging.json +++ b/apps/dashboard/pkg/migration/testdata/input/v15.mimir_rollout_debugging.json @@ -654,7 +654,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": true, "label": "cluster", @@ -677,7 +679,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": false, "label": "namespace", diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v15.mimir_rollout_debugging.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v15.mimir_rollout_debugging.v42.json index 6f507f5bc47..1195b2bec63 100644 --- a/apps/dashboard/pkg/migration/testdata/output/latest_version/v15.mimir_rollout_debugging.v42.json +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v15.mimir_rollout_debugging.v42.json @@ -737,7 +737,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": true, "label": "cluster", @@ -758,7 +760,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": false, "label": "namespace", diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v15.mimir_rollout_debugging.v15.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v15.mimir_rollout_debugging.v15.json index dd9ec62ed85..30186560450 100644 --- a/apps/dashboard/pkg/migration/testdata/output/single_version/v15.mimir_rollout_debugging.v15.json +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v15.mimir_rollout_debugging.v15.json @@ -717,7 +717,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": true, "label": "cluster", @@ -739,7 +741,9 @@ "text": "prod", "value": "prod" }, - "datasource": "$datasource", + "datasource": { + "uid": "$datasource" + }, "hide": 0, "includeAll": false, "label": "namespace", From 72e1f1e5461cdde23a888aea9d101702bfcb701a Mon Sep 17 00:00:00 2001 From: Leon Sorokin Date: Thu, 18 Dec 2025 16:45:00 -0600 Subject: [PATCH 241/297] Heatmap: Support for linear y axis (#113337) * wip * boop * Base factor on data * Add some basic option control * Remove old comments * Add feature flag * Apply feature flag to axis options * Turn factor calculation into exported function * Simplify bucket factor function * Clarify comments * Fix cell sizing of pre-bucketed heatmaps with log * Remove unnecessary category change * Consolidate editor for calculate from data no * Update bucket function sanity checks * Wire up scale config from yBucketScale * Hide bucket controls for heatmap cells * Fix splits * Add test coverage * Fix failing test * Add basic util test coverage * Fix tooltip for legacy in linear * Fix y bucket option width to be consistent * Hide tick alignment for explicit scale modes * Clarify comment * Make sure units are passed properly for linear * Remove null assertion operator * Clean up nested ternary * Add type protection to scaleLog * Remove repeated code for ySize calcs * Remove ternary for scaleDistribution * Add test coverage for YBucketScaleEditor * Add isHeatmapSparse function to tooltip utils * Create calculateYSizeDivisor util function * Fix y axis min and max options and extend to log * Add toLogBase test coverage * Create applyExplicitMinMax function * Add additional test coverage for scale editor * Run i18n-extract * Update eslint suppressions --------- Co-authored-by: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com> --- eslint-suppressions.json | 2 +- .../src/types/featureToggles.gen.ts | 5 + .../panelcfg/x/HeatmapPanelCfg_types.gen.ts | 4 + pkg/services/featuremgmt/registry.go | 8 + pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.json | 14 + .../calculateHeatmap/heatmap.test.ts | 327 ++++++++++++++- .../transformers/calculateHeatmap/heatmap.ts | 158 ++++++-- .../plugins/panel/heatmap/HeatmapPanel.tsx | 16 +- .../plugins/panel/heatmap/HeatmapTooltip.tsx | 15 +- .../panel/heatmap/YBucketScaleEditor.test.tsx | 277 +++++++++++++ .../panel/heatmap/YBucketScaleEditor.tsx | 135 +++++++ public/app/plugins/panel/heatmap/module.tsx | 26 +- public/app/plugins/panel/heatmap/panelcfg.cue | 2 + .../app/plugins/panel/heatmap/panelcfg.gen.ts | 4 + .../panel/heatmap/tooltip/utils.test.ts | 47 +++ .../plugins/panel/heatmap/tooltip/utils.ts | 14 +- .../app/plugins/panel/heatmap/utils.test.ts | 375 +++++++++++++++++- public/app/plugins/panel/heatmap/utils.ts | 115 +++++- public/locales/en-US/grafana.json | 13 + 20 files changed, 1481 insertions(+), 77 deletions(-) create mode 100644 public/app/plugins/panel/heatmap/YBucketScaleEditor.test.tsx create mode 100644 public/app/plugins/panel/heatmap/YBucketScaleEditor.tsx create mode 100644 public/app/plugins/panel/heatmap/tooltip/utils.test.ts diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 1cdf9de4c4a..1d0d5a2684c 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -4339,7 +4339,7 @@ }, "public/app/plugins/panel/heatmap/utils.ts": { "@typescript-eslint/consistent-type-assertions": { - "count": 16 + "count": 14 } }, "public/app/plugins/panel/histogram/Histogram.tsx": { diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index e32ce90bcc5..199be6d9c3f 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -1169,6 +1169,11 @@ export interface FeatureToggles { */ externalVizSuggestions?: boolean; /** + * Enable Y-axis scale configuration options for pre-bucketed heatmap data (heatmap-rows) + * @default false + */ + heatmapRowsAxisOptions?: boolean; + /** * Restrict PanelChrome contents with overflow: hidden; * @default true */ diff --git a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts index 1ff4370c9a2..b67a8cf5980 100644 --- a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts @@ -185,6 +185,10 @@ export interface RowsHeatmapOptions { * Sets the name of the cell when not calculating from data */ value?: string; + /** + * Controls the scale distribution of the y-axis buckets + */ + yBucketScale?: ui.ScaleDistributionConfig; } export interface Options { diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index bd2707f3356..e9a4a8c7caf 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1928,6 +1928,14 @@ var ( Owner: grafanaDatavizSquad, Expression: "false", }, + { + Name: "heatmapRowsAxisOptions", + Description: "Enable Y-axis scale configuration options for pre-bucketed heatmap data (heatmap-rows)", + Stage: FeatureStageExperimental, + FrontendOnly: true, + Owner: grafanaDatavizSquad, + Expression: "false", + }, { Name: "preventPanelChromeOverflow", Description: "Restrict PanelChrome contents with overflow: hidden;", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 095350eb0c8..d56aff17bb7 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -262,6 +262,7 @@ pluginInstallAPISync,experimental,@grafana/plugins-platform-backend,false,false, newGauge,experimental,@grafana/dataviz-squad,false,false,true newVizSuggestions,preview,@grafana/dataviz-squad,false,false,true externalVizSuggestions,experimental,@grafana/dataviz-squad,false,false,true +heatmapRowsAxisOptions,experimental,@grafana/dataviz-squad,false,false,true preventPanelChromeOverflow,preview,@grafana/grafana-frontend-platform,false,false,true jaegerEnableGrpcEndpoint,experimental,@grafana/oss-big-tent,false,false,false pluginStoreServiceLoading,experimental,@grafana/plugins-platform-backend,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 0de187cce52..a0afcd87626 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1647,6 +1647,20 @@ "codeowner": "@grafana/search-and-storage" } }, + { + "metadata": { + "name": "heatmapRowsAxisOptions", + "resourceVersion": "1765353244400", + "creationTimestamp": "2025-12-10T07:54:04Z" + }, + "spec": { + "description": "Enable Y-axis scale configuration options for pre-bucketed heatmap data (heatmap-rows)", + "stage": "experimental", + "codeowner": "@grafana/dataviz-squad", + "frontend": true, + "expression": "false" + } + }, { "metadata": { "name": "improvedExternalSessionHandling", diff --git a/public/app/features/transformers/calculateHeatmap/heatmap.test.ts b/public/app/features/transformers/calculateHeatmap/heatmap.test.ts index 4155a763d4d..d0a963ee9f7 100644 --- a/public/app/features/transformers/calculateHeatmap/heatmap.test.ts +++ b/public/app/features/transformers/calculateHeatmap/heatmap.test.ts @@ -1,7 +1,7 @@ import { FieldType, toDataFrame } from '@grafana/data'; -import { HeatmapCalculationOptions } from '@grafana/schema'; +import { HeatmapCalculationOptions, HeatmapCellLayout, ScaleDistribution } from '@grafana/schema'; -import { rowsToCellsHeatmap, calculateHeatmapFromData } from './heatmap'; +import { rowsToCellsHeatmap, calculateHeatmapFromData, calculateBucketFactor } from './heatmap'; describe('Heatmap transformer', () => { it('calculate heatmap from input data', async () => { @@ -121,4 +121,327 @@ describe('Heatmap transformer', () => { }) ).toThrowErrorMatchingInlineSnapshot(`"No numeric fields found for heatmap"`); }); + + describe('calculateBucketFactor', () => { + it('calculates ratio from last two buckets for log2 spacing', () => { + const buckets = [1, 2, 4, 8]; + expect(calculateBucketFactor(buckets)).toBe(2); + }); + + it('calculates ratio from last two buckets for log10 spacing', () => { + const buckets = [1, 10, 100, 1000]; + expect(calculateBucketFactor(buckets)).toBe(10); + }); + + it('calculates ratio for non-uniform spacing', () => { + const buckets = [1, 2.5, 6.25]; + expect(calculateBucketFactor(buckets)).toBe(2.5); + }); + + it('returns default factor for single value array', () => { + expect(calculateBucketFactor([5])).toBe(1.5); + }); + + it('returns default factor for empty array', () => { + expect(calculateBucketFactor([])).toBe(1.5); + }); + + it('returns default factor when ratio is not valid expansion (<=1)', () => { + const buckets = [10, 5]; // Descending + expect(calculateBucketFactor(buckets)).toBe(1.5); + }); + + it('returns default factor when ratio contains zero', () => { + const buckets = [0, 5]; + expect(calculateBucketFactor(buckets)).toBe(1.5); + }); + + it('returns default factor when ratio is infinite', () => { + const buckets = [5, Infinity]; + expect(calculateBucketFactor(buckets)).toBe(1.5); + }); + + it('accepts custom default factor', () => { + expect(calculateBucketFactor([5], 3)).toBe(3); + }); + }); + + describe('rowsToCellsHeatmap with linear scale', () => { + it('converts prometheus-style le labels to numeric buckets with linear scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000, 2000] }, + { + name: '1', + type: FieldType.number, + labels: { le: '1' }, + values: [10, 15], + }, + { + name: '10', + type: FieldType.number, + labels: { le: '10' }, + values: [20, 25], + }, + { + name: '100', + type: FieldType.number, + labels: { le: '100' }, + values: [30, 35], + }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + }); + + expect(heatmap.fields[1].name).toBe('yMin'); + expect(heatmap.fields[1].values).toEqual([1, 10, 100, 1, 10, 100]); + }); + + it('converts ge labels to numeric buckets with linear scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000, 2000] }, + { + name: '1', + type: FieldType.number, + labels: { ge: '1' }, + values: [10, 15], + }, + { + name: '10', + type: FieldType.number, + labels: { ge: '10' }, + values: [20, 25], + }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + layout: HeatmapCellLayout.ge, + }); + + expect(heatmap.fields[1].values).toEqual([1, 10, 1, 10]); + expect(heatmap.fields[1].name).toBe('yMin'); // ge layout + }); + + it('generates yMax field for linear scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '2', type: FieldType.number, values: [20] }, + { name: '4', type: FieldType.number, values: [30] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + }); + + // Should have yMin, yMax, and count fields + expect(heatmap.fields.length).toBe(4); + expect(heatmap.fields[2].name).toBe('yMax'); + expect(heatmap.fields[2].type).toBe('number'); + + // yMax should be [2, 4, 8] (shifted buckets + calculated last bucket) + // Last bucket uses factor 2 (from 2→4) to estimate 4→8 + expect(heatmap.fields[2].values).toEqual([2, 4, 8]); + }); + + it('clears yOrdinalDisplay for linear scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '10', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + }); + + expect(heatmap.meta?.custom?.yOrdinalDisplay).toBeUndefined(); + }); + + it('clears yOrdinalDisplay for log scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '10', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Log, log: 10 }, + }); + + expect(heatmap.meta?.custom?.yOrdinalDisplay).toBeUndefined(); + }); + + it('clears yOrdinalDisplay for symlog scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '10', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Symlog, log: 10, linearThreshold: 1 }, + }); + + expect(heatmap.meta?.custom?.yOrdinalDisplay).toBeUndefined(); + }); + + it('preserves yOrdinalDisplay for non-numeric scale (auto/ordinal)', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: 'low', type: FieldType.number, values: [10] }, + { name: 'high', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ frame }); + + expect(heatmap.meta?.custom?.yOrdinalDisplay).toEqual(['low', 'high']); + }); + + it('sets unit to undefined for linear scale when no unit exists', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '10', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + }); + + // No unit → expect undefined (not 'short') + expect(heatmap.fields[1].config.unit).toBeUndefined(); + }); + + it('passes through existing unit for linear scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10], config: { unit: 'ms' } }, + { name: '10', type: FieldType.number, values: [20], config: { unit: 'ms' } }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + }); + + // Existing unit → pass through unchanged + expect(heatmap.fields[1].config.unit).toBe('ms'); + }); + + it('sets unit to short for ordinal scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: 'low', type: FieldType.number, values: [10] }, + { name: 'high', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ frame }); + + expect(heatmap.fields[1].config.unit).toBe('short'); + }); + + it('uses "count" as value field name for linear scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '10', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + }); + + // Without yMax, should be 3 fields: xMax, y/yMin/yMax, yMax, count + const valueField = heatmap.fields.find((f) => f.name === 'count'); + expect(valueField).toBeDefined(); + }); + + it('uses "Value" as field name for ordinal scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: 'low', type: FieldType.number, values: [10] }, + { name: 'high', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ frame }); + + const valueField = heatmap.fields.find((f) => f.name === 'Value'); + expect(valueField).toBeDefined(); + }); + + it('respects custom value field name for linear scale', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '10', type: FieldType.number, values: [20] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + value: 'Temperature', + }); + + const valueField = heatmap.fields.find((f) => f.name === 'Temperature'); + expect(valueField).toBeDefined(); + }); + + it('calculates yMax upper bound using bucket factor', () => { + const frame = toDataFrame({ + fields: [ + { name: 'time', type: FieldType.time, values: [1000] }, + { name: '1', type: FieldType.number, values: [10] }, + { name: '10', type: FieldType.number, values: [20] }, + { name: '100', type: FieldType.number, values: [30] }, + ], + }); + + const heatmap = rowsToCellsHeatmap({ + frame, + yBucketScale: { type: ScaleDistribution.Linear }, + }); + + // buckets: [1, 10, 100] + // yMax: [10, 100, 1000] - last one calculated as 100 * 10 + const yMaxField = heatmap.fields.find((f) => f.name === 'yMax'); + expect(yMaxField?.values).toEqual([10, 100, 1000]); + }); + }); }); diff --git a/public/app/features/transformers/calculateHeatmap/heatmap.ts b/public/app/features/transformers/calculateHeatmap/heatmap.ts index 49a54bb43f1..43640708eae 100644 --- a/public/app/features/transformers/calculateHeatmap/heatmap.ts +++ b/public/app/features/transformers/calculateHeatmap/heatmap.ts @@ -19,6 +19,7 @@ import { isLikelyAscendingVector } from '@grafana/data/internal'; import { t } from '@grafana/i18n'; import { ScaleDistribution, + ScaleDistributionConfig, HeatmapCellLayout, HeatmapCalculationMode, HeatmapCalculationOptions, @@ -72,13 +73,36 @@ function parseNumeric(v?: string | null) { return v === '+Inf' ? Infinity : v === '-Inf' ? -Infinity : +(v ?? 0); } +/** + * Calculate the expansion factor from adjacent bucket values. + * This is used to estimate the size/bound of the next bucket based on the spacing of existing buckets. + * + * @param bucketValues - Array of bucket boundary values + * @param defaultFactor - Factor to use if ratio cannot be determined (default: 1.5 for 50% expansion) + * @returns The calculated or default expansion factor + */ +export function calculateBucketFactor(bucketValues: number[], defaultFactor = 1.5): number { + if (bucketValues.length >= 2) { + const last = bucketValues.at(-1)!; + const prev = bucketValues.at(-2)!; + const ratio = last / prev; + + // Only use ratio if it represents expansion (>1) and is valid + if (ratio > 1 && Number.isFinite(ratio)) { + return ratio; + } + } + + return defaultFactor; +} + export function sortAscStrInf(aName?: string | null, bName?: string | null) { return parseNumeric(aName) - parseNumeric(bName); } export interface HeatmapRowsCustomMeta { /** This provides the lookup values */ - yOrdinalDisplay: string[]; + yOrdinalDisplay?: string[]; yOrdinalLabel?: string[]; yMatchWithLabel?: string; yMinDisplay?: string; @@ -115,6 +139,7 @@ export interface RowsHeatmapOptions { unit?: string; decimals?: number; layout?: HeatmapCellLayout; + yBucketScale?: ScaleDistributionConfig; } /** Given existing buckets, create a values style frame */ @@ -129,10 +154,19 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame { throw new Error(t('heatmap.error.no-y-fields', 'No numeric fields found for heatmap')); } + // Determine if we should use numeric scaling based on yBucketScale option + // Default to 'auto' behavior (ordinal) if not specified + const scaleType = opts.yBucketScale?.type; + const useNumericScale = + scaleType === ScaleDistribution.Linear || + scaleType === ScaleDistribution.Log || + scaleType === ScaleDistribution.Symlog; + // similar to initBins() below const len = xValues.length * yFields.length; const xs = new Array(len); const ys = new Array(len); + const ys2 = useNumericScale ? new Array(len) : undefined; const counts2 = new Array(len); const counts = yFields.map((field) => field.values.slice()); @@ -144,21 +178,8 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame { } }); - const bucketBounds = Array.from({ length: yFields.length }, (v, i) => i); - - // fill flat/repeating array - for (let i = 0, yi = 0, xi = 0; i < len; yi = ++i % bucketBounds.length) { - ys[i] = bucketBounds[yi]; - - if (yi === 0 && i >= bucketBounds.length) { - xi++; - } - - xs[i] = xValues[xi]; - } - // this name determines whether cells are drawn above, below, or centered on the values - let ordinalFieldName = yFields[0].labels?.le != null ? 'yMax' : 'y'; + let ordinalFieldName = yFields[0].labels?.le != null ? 'yMax' : yFields[0].labels?.ge != null ? 'yMin' : 'y'; switch (opts.layout) { case HeatmapCellLayout.le: ordinalFieldName = 'yMax'; @@ -175,6 +196,45 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame { yOrdinalDisplay: yFields.map((f) => getFieldDisplayName(f, opts.frame)), yMatchWithLabel: Object.keys(yFields[0].labels ?? {})[0], }; + + let bucketBounds: number[]; + let bucketBoundsMax: number[] | undefined; + + if (useNumericScale) { + // Numeric mode: use numeric bucket values + bucketBounds = yFields.map((field) => { + const labelKey = custom.yMatchWithLabel; + const labelValue = labelKey ? field.labels?.[labelKey] : undefined; + const valueStr = labelValue ?? field.name; + return Number(valueStr); + }); + + // Generate upper bounds: shift values + calculate last bucket + bucketBoundsMax = bucketBounds.slice(); + bucketBoundsMax.shift(); + const factor = calculateBucketFactor(bucketBounds); + bucketBoundsMax.push(bucketBounds[bucketBounds.length - 1] * factor); + + custom.yMatchWithLabel = undefined; + } else { + // Auto mode: use ordinal indices like the original main branch behavior + bucketBounds = Array.from({ length: yFields.length }, (v, i) => i); + } + + // fill flat/repeating array + for (let i = 0, yi = 0, xi = 0; i < len; yi = ++i % bucketBounds.length) { + ys[i] = bucketBounds[yi]; + if (useNumericScale && ys2 && bucketBoundsMax) { + ys2[i] = bucketBoundsMax[yi]; + } + + if (yi === 0 && i >= bucketBounds.length) { + xi++; + } + + xs[i] = xValues[xi]; + } + if (custom.yMatchWithLabel) { custom.yOrdinalLabel = yFields.map((f) => f.labels?.[custom.yMatchWithLabel!] ?? ''); if (custom.yMatchWithLabel === 'le') { @@ -189,7 +249,7 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame { if (custom.yMinDisplay) { custom.yMinDisplay = formattedValueToString(fmt(0, opts.decimals)); } - custom.yOrdinalDisplay = custom.yOrdinalDisplay.map((name) => { + custom.yOrdinalDisplay = custom.yOrdinalDisplay?.map((name) => { let num = +name; if (!Number.isNaN(num)) { @@ -200,6 +260,11 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame { }); } + // Clear yOrdinalDisplay when using numeric scales (linear, log, symlog) + if (useNumericScale) { + custom.yOrdinalDisplay = undefined; + } + const valueCfg = { ...yFields[0].config, }; @@ -208,6 +273,43 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame { delete valueCfg.displayNameFromDS; } + // Build fields array - only include yMax in linear scale mode + const fields: Field[] = [ + { + name: xField.type === FieldType.time ? 'xMax' : 'x', + type: xField.type, + values: xs, + config: xField.config, + }, + { + name: useNumericScale ? 'yMin' : ordinalFieldName, + type: FieldType.number, + values: ys, + config: { + unit: useNumericScale ? yFields[0]?.config?.unit : 'short', // preserve original unit for numeric, use 'short' for ordinal + }, + }, + ]; + + // yMax provides explicit upper bounds for proper rendering, critical for ge layout + if (useNumericScale && ys2) { + fields.push({ + name: 'yMax', + type: FieldType.number, + values: ys2, + config: {}, + }); + } + + // Add value/count field + fields.push({ + name: opts.value?.length ? opts.value : useNumericScale ? 'count' : 'Value', + type: FieldType.number, + values: counts2, + config: valueCfg, + display: yFields[0].display, + }); + return { length: xs.length, refId: opts.frame.refId, @@ -215,29 +317,7 @@ export function rowsToCellsHeatmap(opts: RowsHeatmapOptions): DataFrame { type: DataFrameType.HeatmapCells, custom, }, - fields: [ - { - name: xField.type === FieldType.time ? 'xMax' : 'x', - type: xField.type, - values: xs, - config: xField.config, - }, - { - name: ordinalFieldName, - type: FieldType.number, - values: ys, - config: { - unit: 'short', // ordinal lookup - }, - }, - { - name: opts.value?.length ? opts.value : 'Value', - type: FieldType.number, - values: counts2, - config: valueCfg, - display: yFields[0].display, - }, - ], + fields, }; } diff --git a/public/app/plugins/panel/heatmap/HeatmapPanel.tsx b/public/app/plugins/panel/heatmap/HeatmapPanel.tsx index 4d2762a9be3..64efef6c5aa 100644 --- a/public/app/plugins/panel/heatmap/HeatmapPanel.tsx +++ b/public/app/plugins/panel/heatmap/HeatmapPanel.tsx @@ -5,7 +5,6 @@ import { DashboardCursorSync, PanelProps, TimeRange } from '@grafana/data'; import { PanelDataErrorView } from '@grafana/runtime'; import { ScaleDistributionConfig } from '@grafana/schema'; import { - ScaleDistribution, TooltipPlugin2, TooltipDisplayMode, UPlotChart, @@ -29,7 +28,7 @@ import { HeatmapTooltip } from './HeatmapTooltip'; import { HeatmapData, prepareHeatmapData } from './fields'; import { quantizeScheme } from './palettes'; import { Options } from './types'; -import { prepConfig } from './utils'; +import { calculateYSizeDivisor, prepConfig } from './utils'; interface HeatmapPanelProps extends PanelProps {} @@ -141,6 +140,16 @@ const HeatmapPanelViz = ({ const builder = useMemo(() => { const scaleConfig: ScaleDistributionConfig = dataRef.current?.heatmap?.fields[1].config?.custom?.scaleDistribution; + const activeScaleConfig = options.rowsFrame?.yBucketScale ?? scaleConfig; + + // For log/symlog scales: use 1 for pre-bucketed data with explicit scale, otherwise use split value + const hasExplicitScale = options.rowsFrame?.yBucketScale !== undefined; + const ySizeDivisor = calculateYSizeDivisor( + activeScaleConfig?.type, + hasExplicitScale, + options.calculation?.yBuckets?.value + ); + return prepConfig({ dataRef, theme, @@ -151,9 +160,10 @@ const HeatmapPanelViz = ({ hideGE: options.filterValues?.ge, exemplarColor: options.exemplars?.color ?? 'rgba(255,0,255,0.7)', yAxisConfig: options.yAxis, - ySizeDivisor: scaleConfig?.type === ScaleDistribution.Log ? +(options.calculation?.yBuckets?.value || 1) : 1, + ySizeDivisor, selectionMode: options.selectionMode, xAxisConfig: getXAxisConfig(annotationsLength), + rowsFrame: options.rowsFrame, }); // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx b/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx index 480d36a5a6a..269243f1ad8 100644 --- a/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx +++ b/public/app/plugins/panel/heatmap/HeatmapTooltip.tsx @@ -4,7 +4,6 @@ import uPlot from 'uplot'; import { ActionModel, - DataFrameType, Field, FieldType, formattedValueToString, @@ -26,7 +25,7 @@ import { } from '@grafana/ui/internal'; import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; -import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap'; +import { readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap'; import { getDisplayValuesAndLinks } from 'app/features/visualization/data-hover/DataHoverView'; import { ExemplarTooltip } from 'app/features/visualization/data-hover/ExemplarTooltip'; @@ -35,7 +34,13 @@ import { isTooltipScrollable } from '../timeseries/utils'; import { HeatmapData } from './fields'; import { renderHistogram } from './renderHistogram'; -import { formatMilliseconds, getFieldFromData, getHoverCellColor, getSparseCellMinMax } from './tooltip/utils'; +import { + formatMilliseconds, + getFieldFromData, + getHoverCellColor, + getSparseCellMinMax, + isHeatmapSparse, +} from './tooltip/utils'; interface HeatmapTooltipProps { mode: TooltipDisplayMode; @@ -99,9 +104,7 @@ const HeatmapHoverCell = ({ const index = dataIdxs[1]!; const data = dataRef.current; - const [isSparse] = useState( - () => data.heatmap?.meta?.type === DataFrameType.HeatmapCells && !isHeatmapCellsDense(data.heatmap) - ); + const [isSparse] = useState(() => isHeatmapSparse(data.heatmap)); const xField = getFieldFromData(data.heatmap!, 'x', isSparse)!; const yField = getFieldFromData(data.heatmap!, 'y', isSparse)!; diff --git a/public/app/plugins/panel/heatmap/YBucketScaleEditor.test.tsx b/public/app/plugins/panel/heatmap/YBucketScaleEditor.test.tsx new file mode 100644 index 00000000000..6c0ff4b804c --- /dev/null +++ b/public/app/plugins/panel/heatmap/YBucketScaleEditor.test.tsx @@ -0,0 +1,277 @@ +import { render, screen, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import selectEvent from 'react-select-event'; + +import { StandardEditorContext, StandardEditorsRegistryItem } from '@grafana/data'; +import { ScaleDistribution, ScaleDistributionConfig } from '@grafana/schema'; + +import { YBucketScaleEditor } from './YBucketScaleEditor'; + +const mockContext: StandardEditorContext = { + data: [], +}; + +const mockItem: StandardEditorsRegistryItem = { + id: 'yBucketScale', + name: 'Y Bucket Scale', + editor: YBucketScaleEditor, +}; + +describe('YBucketScaleEditor', () => { + describe('Scale selection', () => { + it('should render with Auto selected when value is undefined', () => { + const onChange = jest.fn(); + render(); + + const autoButton = screen.getByRole('radio', { name: /auto/i }); + expect(autoButton).toBeChecked(); + }); + + it('should render with Linear selected when value is Linear', () => { + const onChange = jest.fn(); + render( + + ); + + const linearButton = screen.getByRole('radio', { name: /linear/i }); + expect(linearButton).toBeChecked(); + }); + + it('should call onChange with undefined when Auto is selected', async () => { + const onChange = jest.fn(); + render( + + ); + + const autoButton = screen.getByRole('radio', { name: /auto/i }); + await userEvent.click(autoButton); + + expect(onChange).toHaveBeenCalledWith(undefined); + }); + + it('should call onChange with Linear config when Linear is selected', async () => { + const onChange = jest.fn(); + render(); + + const linearButton = screen.getByRole('radio', { name: /linear/i }); + await userEvent.click(linearButton); + + expect(onChange).toHaveBeenCalledWith({ type: ScaleDistribution.Linear }); + }); + + it('should call onChange with Log config when Log is selected', async () => { + const onChange = jest.fn(); + render(); + + const logButton = screen.getByRole('radio', { name: /^log$/i }); + await userEvent.click(logButton); + + expect(onChange).toHaveBeenCalledWith({ type: ScaleDistribution.Log, log: 2 }); + }); + + it('should call onChange with Symlog config when Symlog is selected', async () => { + const onChange = jest.fn(); + render(); + + const symlogButton = screen.getByRole('radio', { name: /symlog/i }); + await userEvent.click(symlogButton); + + expect(onChange).toHaveBeenCalledWith({ type: ScaleDistribution.Symlog, log: 2, linearThreshold: 1 }); + }); + }); + + describe('Log base selection', () => { + it('should show log base selector for Log scale', () => { + const onChange = jest.fn(); + render( + + ); + + expect(screen.getByText('Log base')).toBeInTheDocument(); + }); + + it('should show log base selector for Symlog scale', () => { + const onChange = jest.fn(); + render( + + ); + + expect(screen.getByText('Log base')).toBeInTheDocument(); + }); + + it('should not show log base selector for Linear scale', () => { + const onChange = jest.fn(); + render( + + ); + + expect(screen.queryByText('Log base')).not.toBeInTheDocument(); + }); + + it('should not show log base selector for Auto', () => { + const onChange = jest.fn(); + render(); + + expect(screen.queryByText('Log base')).not.toBeInTheDocument(); + }); + + it('should preserve existing log base when switching to Log', async () => { + const onChange = jest.fn(); + render( + + ); + + const logButton = screen.getByRole('radio', { name: /^log$/i }); + await userEvent.click(logButton); + + expect(onChange).toHaveBeenCalledWith({ type: ScaleDistribution.Log, log: 10 }); + }); + + it('should update log base when changed for Log scale', async () => { + const onChange = jest.fn(); + render( + + ); + + // Find the log base field container and query the combobox within it + const logBaseLabel = screen.getByText('Log base'); + const fieldContainer = logBaseLabel.closest('div[style]') as HTMLElement; // The div with style="margin-top: 8px;" + const selectEl = within(fieldContainer).getByRole('combobox'); + + await selectEvent.select(selectEl, '10', { container: document.body }); + + expect(onChange).toHaveBeenCalledWith({ type: ScaleDistribution.Log, log: 10 }); + }); + + it('should update log base when changed for Symlog scale', async () => { + const onChange = jest.fn(); + render( + + ); + + // Find the log base field container and query the combobox within it + const logBaseLabel = screen.getByText('Log base'); + const fieldContainer = logBaseLabel.closest('div[style]') as HTMLElement; // The div with style="margin-top: 8px;" + const selectEl = within(fieldContainer).getByRole('combobox'); + + await selectEvent.select(selectEl, '10', { container: document.body }); + + expect(onChange).toHaveBeenCalledWith({ type: ScaleDistribution.Symlog, log: 10, linearThreshold: 1 }); + }); + }); + + describe('Linear threshold', () => { + it('should show linear threshold input for Symlog scale', () => { + const onChange = jest.fn(); + render( + + ); + + expect(screen.getByText('Linear threshold')).toBeInTheDocument(); + }); + + it('should not show linear threshold input for Log scale', () => { + const onChange = jest.fn(); + render( + + ); + + expect(screen.queryByText('Linear threshold')).not.toBeInTheDocument(); + }); + + it('should not update linear threshold for a 0 value', async () => { + const onChange = jest.fn(); + const origValue = { type: ScaleDistribution.Symlog, log: 10, linearThreshold: 1 }; + + render(); + + const input = screen.getByPlaceholderText('1'); + + await userEvent.clear(input); + await userEvent.type(input, '0'); + expect(onChange).not.toHaveBeenCalled(); + + await userEvent.type(input, '.'); + expect(onChange).not.toHaveBeenCalled(); + + await userEvent.type(input, '5'); + expect(onChange).toHaveBeenCalledWith({ ...origValue, linearThreshold: 0.5 }); + }); + + it('should update linear threshold for valid non-zero values', async () => { + const onChange = jest.fn(); + const origValue = { type: ScaleDistribution.Symlog, log: 2, linearThreshold: 1 }; + + render(); + + const input = screen.getByPlaceholderText('1'); + + await userEvent.clear(input); + await userEvent.type(input, '5'); + expect(onChange).toHaveBeenCalledWith({ ...origValue, linearThreshold: 5 }); + }); + + it('should not dispatch onChange for invalid input', async () => { + const onChange = jest.fn(); + const origValue = { type: ScaleDistribution.Symlog, log: 2, linearThreshold: 1 }; + + render(); + + const input = screen.getByPlaceholderText('1'); + + await userEvent.clear(input); + await userEvent.type(input, 'abc'); + expect(onChange).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/public/app/plugins/panel/heatmap/YBucketScaleEditor.tsx b/public/app/plugins/panel/heatmap/YBucketScaleEditor.tsx new file mode 100644 index 00000000000..fc96101f9bd --- /dev/null +++ b/public/app/plugins/panel/heatmap/YBucketScaleEditor.tsx @@ -0,0 +1,135 @@ +import { useState } from 'react'; + +import { SelectableValue, StandardEditorProps } from '@grafana/data'; +import { t } from '@grafana/i18n'; +import { ScaleDistribution, ScaleDistributionConfig } from '@grafana/schema'; +import { RadioButtonGroup, Field, Select, Input } from '@grafana/ui'; + +type ScaleOptionValue = 'auto' | ScaleDistribution; + +/** + * Simplified scale editor that shows all options in a single line. + * Includes "Auto" option which returns undefined to use default behavior. + */ +export const YBucketScaleEditor = (props: StandardEditorProps) => { + const { value, onChange } = props; + + const type = value?.type; + const log = value?.log ?? 2; + const isAuto = value === undefined; + + const [localLinearThreshold, setLocalLinearThreshold] = useState( + value?.linearThreshold != null ? String(value.linearThreshold) : '' + ); + + const currentOption: ScaleOptionValue = isAuto ? 'auto' : type!; + const showLogBase = type === ScaleDistribution.Log || type === ScaleDistribution.Symlog; + const showLinearThreshold = type === ScaleDistribution.Symlog; + + const SCALE_OPTIONS: Array> = [ + { + label: t('heatmap.y-bucket-scale-editor.scale-options.label-auto', 'Auto'), + value: 'auto', + }, + { + label: t('heatmap.y-bucket-scale-editor.scale-options.label-linear', 'Linear'), + value: ScaleDistribution.Linear, + }, + { + label: t('heatmap.y-bucket-scale-editor.scale-options.label-log', 'Log'), + value: ScaleDistribution.Log, + }, + { + label: t('heatmap.y-bucket-scale-editor.scale-options.label-symlog', 'Symlog'), + value: ScaleDistribution.Symlog, + }, + ]; + + const LOG_BASE_OPTIONS: Array> = [ + { + label: '2', + value: 2, + }, + { + label: '10', + value: 10, + }, + ]; + + const handleScaleChange = (v: ScaleOptionValue) => { + if (v === 'auto') { + onChange(undefined); + return; + } + + if (v === ScaleDistribution.Linear) { + onChange({ type: ScaleDistribution.Linear }); + return; + } + + if (v === ScaleDistribution.Log) { + onChange({ type: ScaleDistribution.Log, log }); + return; + } + + if (v === ScaleDistribution.Symlog) { + onChange({ + type: ScaleDistribution.Symlog, + log, + linearThreshold: value?.linearThreshold ?? 1, + }); + return; + } + }; + + const handleLogBaseChange = (newLog: number) => { + onChange({ + ...value!, + log: newLog, + }); + }; + + const handleLinearThresholdChange = (newValue: string) => { + setLocalLinearThreshold(newValue); + const numValue = parseFloat(newValue); + if (!isNaN(numValue) && numValue !== 0) { + onChange({ + ...value!, + linearThreshold: numValue, + }); + } + }; + + return ( + <> + + {showLogBase && ( + + handleLinearThresholdChange(e.currentTarget.value)} + placeholder={t('heatmap.y-bucket-scale-editor.linear-threshold-placeholder', '1')} + /> + + )} + + ); +}; diff --git a/public/app/plugins/panel/heatmap/module.tsx b/public/app/plugins/panel/heatmap/module.tsx index a7d44726886..99bb66e2c3c 100644 --- a/public/app/plugins/panel/heatmap/module.tsx +++ b/public/app/plugins/panel/heatmap/module.tsx @@ -1,4 +1,11 @@ -import { DataFrame, FieldConfigProperty, FieldType, identityOverrideProcessor, PanelPlugin } from '@grafana/data'; +import { + DataFrame, + DataFrameType, + FieldConfigProperty, + FieldType, + identityOverrideProcessor, + PanelPlugin, +} from '@grafana/data'; import { t } from '@grafana/i18n'; import { config } from '@grafana/runtime'; import { @@ -15,6 +22,7 @@ import { addHeatmapCalculationOptions } from 'app/features/transformers/calculat import { readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap'; import { HeatmapPanel } from './HeatmapPanel'; +import { YBucketScaleEditor } from './YBucketScaleEditor'; import { prepareHeatmapData } from './fields'; import { heatmapChangedHandler, heatmapMigrationHandler } from './migrations'; import { colorSchemes, quantizeScheme } from './palettes'; @@ -59,6 +67,7 @@ export const plugin = new PanelPlugin(HeatmapPanel) const opts = context.options ?? defaultOptions; let isOrdinalY = false; + const isHeatmapCells = context.data.some((frame) => frame.meta?.type === DataFrameType.HeatmapCells); if (context.data.length > 0) { try { @@ -94,6 +103,17 @@ export const plugin = new PanelPlugin(HeatmapPanel) addHeatmapCalculationOptions('calculation.', builder, opts.calculation, category); } + if (!opts.calculate && !isHeatmapCells && config.featureToggles.heatmapRowsAxisOptions) { + builder.addCustomEditor({ + id: 'rowsFrame-yBucketScale', + path: 'rowsFrame.yBucketScale', + name: t('heatmap.name-y-bucket-scale', 'Y bucket scale'), + category, + editor: YBucketScaleEditor, + defaultValue: undefined, + }); + } + category = [t('heatmap.category-y-axis', 'Y Axis')]; builder @@ -170,7 +190,9 @@ export const plugin = new PanelPlugin(HeatmapPanel) category, }); - if (!opts.calculate) { + // Hide tick alignment for explicit scales - bucket boundaries are fixed by numeric labels + const hasExplicitScale = context.options?.rowsFrame?.yBucketScale !== undefined; + if (!opts.calculate && !hasExplicitScale) { builder.addRadio({ path: 'rowsFrame.layout', name: t('heatmap.name-tick-alignment', 'Tick alignment'), diff --git a/public/app/plugins/panel/heatmap/panelcfg.cue b/public/app/plugins/panel/heatmap/panelcfg.cue index e947aaf9bb6..e07717429d7 100644 --- a/public/app/plugins/panel/heatmap/panelcfg.cue +++ b/public/app/plugins/panel/heatmap/panelcfg.cue @@ -105,6 +105,8 @@ composableKinds: PanelCfg: lineage: { value?: string // Controls tick alignment when not calculating from data layout?: ui.HeatmapCellLayout + // Controls the scale distribution of the y-axis buckets + yBucketScale?: ui.ScaleDistributionConfig } @cuetsy(kind="interface") Options: { annotations?: ui.VizAnnotations diff --git a/public/app/plugins/panel/heatmap/panelcfg.gen.ts b/public/app/plugins/panel/heatmap/panelcfg.gen.ts index ef3ab0c9459..d5b822eb1ab 100644 --- a/public/app/plugins/panel/heatmap/panelcfg.gen.ts +++ b/public/app/plugins/panel/heatmap/panelcfg.gen.ts @@ -183,6 +183,10 @@ export interface RowsHeatmapOptions { * Sets the name of the cell when not calculating from data */ value?: string; + /** + * Controls the scale distribution of the y-axis buckets + */ + yBucketScale?: ui.ScaleDistributionConfig; } export interface Options { diff --git a/public/app/plugins/panel/heatmap/tooltip/utils.test.ts b/public/app/plugins/panel/heatmap/tooltip/utils.test.ts new file mode 100644 index 00000000000..a5da298d75c --- /dev/null +++ b/public/app/plugins/panel/heatmap/tooltip/utils.test.ts @@ -0,0 +1,47 @@ +import { DataFrameType, toDataFrame } from '@grafana/data'; + +import { isHeatmapSparse } from './utils'; + +describe('isHeatmapSparse', () => { + it('should return false when heatmap is undefined', () => { + expect(isHeatmapSparse(undefined)).toBe(false); + }); + + it('should return false for dense HeatmapCells (single Y field)', () => { + const heatmap = toDataFrame({ + fields: [{ name: 'y', values: [] }], + meta: { type: DataFrameType.HeatmapCells }, + }); + + expect(isHeatmapSparse(heatmap)).toBe(false); + }); + + it('should return true for sparse HeatmapCells (yMin and yMax fields)', () => { + const heatmap = toDataFrame({ + fields: [ + { name: 'yMin', values: [] }, + { name: 'yMax', values: [] }, + ], + meta: { type: DataFrameType.HeatmapCells }, + }); + + expect(isHeatmapSparse(heatmap)).toBe(true); + }); + + it('should return false for non-HeatmapCells data frames', () => { + const heatmap = toDataFrame({ + fields: [{ name: 'Value', values: [] }], + meta: { type: DataFrameType.HeatmapRows }, + }); + + expect(isHeatmapSparse(heatmap)).toBe(false); + }); + + it('should return false when meta is undefined', () => { + const heatmap = toDataFrame({ + fields: [{ name: 'value', values: [] }], + }); + + expect(isHeatmapSparse(heatmap)).toBe(false); + }); +}); diff --git a/public/app/plugins/panel/heatmap/tooltip/utils.ts b/public/app/plugins/panel/heatmap/tooltip/utils.ts index 58114245d4e..067bf7d2b71 100644 --- a/public/app/plugins/panel/heatmap/tooltip/utils.ts +++ b/public/app/plugins/panel/heatmap/tooltip/utils.ts @@ -1,4 +1,5 @@ -import { DataFrame, Field } from '@grafana/data'; +import { DataFrame, DataFrameType, Field } from '@grafana/data'; +import { isHeatmapCellsDense } from 'app/features/transformers/calculateHeatmap/heatmap'; import { HeatmapData } from '../fields'; @@ -91,3 +92,14 @@ export const getSparseCellMinMax = (data: HeatmapData, index: number): BucketsMi yBucketMax: yMax.values[index], }; }; + +/** + * Determines if a heatmap DataFrame is sparse (has explicit yMin/yMax bounds). + * Sparse heatmaps have HeatmapCells type and are not dense. + */ +export function isHeatmapSparse(heatmap: DataFrame | undefined): boolean { + if (!heatmap) { + return false; + } + return heatmap.meta?.type === DataFrameType.HeatmapCells && !isHeatmapCellsDense(heatmap); +} diff --git a/public/app/plugins/panel/heatmap/utils.test.ts b/public/app/plugins/panel/heatmap/utils.test.ts index 26fecaad53b..abb10812469 100644 --- a/public/app/plugins/panel/heatmap/utils.test.ts +++ b/public/app/plugins/panel/heatmap/utils.test.ts @@ -1,5 +1,374 @@ -describe('a test', () => { - it('has to have at least one test', () => { - expect(true).toBeTruthy(); +import { ScaleDistribution } from '@grafana/schema'; + +import { applyExplicitMinMax, boundedMinMax, calculateYSizeDivisor, toLogBase, valuesToFills } from './utils'; + +describe('toLogBase', () => { + it('returns 10 when value is 10', () => { + expect(toLogBase(10)).toBe(10); + }); + + it('returns 2 when value is 2', () => { + expect(toLogBase(2)).toBe(2); + }); + + it('returns 2 (default) when value is undefined', () => { + expect(toLogBase(undefined)).toBe(2); + }); + + it('returns 2 (default) for invalid values', () => { + expect(toLogBase(5)).toBe(2); + expect(toLogBase(0)).toBe(2); + expect(toLogBase(-1)).toBe(2); + expect(toLogBase(100)).toBe(2); + }); +}); + +describe('applyExplicitMinMax', () => { + it('returns original values when no explicit values provided', () => { + const [min, max] = applyExplicitMinMax(0, 100, undefined, undefined); + expect(min).toBe(0); + expect(max).toBe(100); + }); + + it('applies explicit min only', () => { + const [min, max] = applyExplicitMinMax(0, 100, 10, undefined); + expect(min).toBe(10); + expect(max).toBe(100); + }); + + it('applies explicit max only', () => { + const [min, max] = applyExplicitMinMax(0, 100, undefined, 90); + expect(min).toBe(0); + expect(max).toBe(90); + }); + + it('applies both explicit min and max', () => { + const [min, max] = applyExplicitMinMax(0, 100, 20, 80); + expect(min).toBe(20); + expect(max).toBe(80); + }); + + it('handles negative values', () => { + const [min, max] = applyExplicitMinMax(-50, 50, -10, 10); + expect(min).toBe(-10); + expect(max).toBe(10); + }); + + it('handles explicit min = 0', () => { + const [min, max] = applyExplicitMinMax(10, 100, 0, undefined); + expect(min).toBe(0); + expect(max).toBe(100); + }); + + it('handles explicit max = 0', () => { + const [min, max] = applyExplicitMinMax(-100, -10, undefined, 0); + expect(min).toBe(-100); + expect(max).toBe(0); + }); + + it('handles null scaleMin', () => { + const [min, max] = applyExplicitMinMax(null, 100, 10, undefined); + expect(min).toBe(10); + expect(max).toBe(100); + }); + + it('handles null scaleMax', () => { + const [min, max] = applyExplicitMinMax(0, null, undefined, 90); + expect(min).toBe(0); + expect(max).toBe(90); + }); + + it('preserves null when no explicit value provided', () => { + const [min, max] = applyExplicitMinMax(null, null, undefined, undefined); + expect(min).toBe(null); + expect(max).toBe(null); + }); +}); + +describe('calculateYSizeDivisor', () => { + it('returns 1 for linear scale', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Linear, false, 2)).toBe(1); + }); + + it('returns 1 for log scale with explicit scale', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Log, true, 2)).toBe(1); + }); + + it('returns 1 for symlog scale with explicit scale', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Symlog, true, 2)).toBe(1); + }); + + it('returns split value for log scale without explicit scale', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Log, false, 2)).toBe(2); + expect(calculateYSizeDivisor(ScaleDistribution.Log, false, 4)).toBe(4); + }); + + it('returns split value for symlog scale without explicit scale', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Symlog, false, 2)).toBe(2); + expect(calculateYSizeDivisor(ScaleDistribution.Symlog, false, 3)).toBe(3); + }); + + it('handles string split values', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Log, false, '2')).toBe(2); + expect(calculateYSizeDivisor(ScaleDistribution.Log, false, '4')).toBe(4); + }); + + it('returns 1 when split value is undefined', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Log, false, undefined)).toBe(1); + }); + + it('returns 1 when scale type is undefined', () => { + expect(calculateYSizeDivisor(undefined, false, 2)).toBe(1); + }); + + it('returns 1 for ordinal scale', () => { + expect(calculateYSizeDivisor(ScaleDistribution.Ordinal, false, 2)).toBe(1); + }); +}); + +describe('boundedMinMax', () => { + describe('when min and max are not provided', () => { + it('calculates min and max from values', () => { + const values = [10, 20, 5, 30, 15]; + const [min, max] = boundedMinMax(values); + expect(min).toBe(5); + expect(max).toBe(30); + }); + + it('handles single value', () => { + const values = [42]; + const [min, max] = boundedMinMax(values); + expect(min).toBe(42); + expect(max).toBe(42); + }); + + it('handles negative values', () => { + const values = [-10, -20, -5, -30]; + const [min, max] = boundedMinMax(values); + expect(min).toBe(-30); + expect(max).toBe(-5); + }); + + it('handles mixed positive and negative values', () => { + const values = [-10, 20, -5, 30]; + const [min, max] = boundedMinMax(values); + expect(min).toBe(-10); + expect(max).toBe(30); + }); + + it('returns Infinity/-Infinity for empty array', () => { + const values: number[] = []; + const [min, max] = boundedMinMax(values); + expect(min).toBe(Infinity); + expect(max).toBe(-Infinity); + }); + }); + + describe('when min is provided', () => { + it('uses provided min value', () => { + const values = [10, 20, 5, 30]; + const [min, max] = boundedMinMax(values, 0); + expect(min).toBe(0); + expect(max).toBe(30); + }); + + it('uses provided min even if higher than data min', () => { + const values = [10, 20, 5, 30]; + const [min, max] = boundedMinMax(values, 15); + expect(min).toBe(15); + expect(max).toBe(30); + }); + }); + + describe('when max is provided', () => { + it('uses provided max value', () => { + const values = [10, 20, 5, 30]; + const [min, max] = boundedMinMax(values, undefined, 50); + expect(min).toBe(5); + expect(max).toBe(50); + }); + + it('uses provided max even if lower than data max', () => { + const values = [10, 20, 5, 30]; + const [min, max] = boundedMinMax(values, undefined, 25); + expect(min).toBe(5); + expect(max).toBe(25); + }); + }); + + describe('when both min and max are provided', () => { + it('uses both provided values', () => { + const values = [10, 20, 5, 30]; + const [min, max] = boundedMinMax(values, 0, 50); + expect(min).toBe(0); + expect(max).toBe(50); + }); + }); + + describe('with hideLE filter', () => { + it('excludes values less than or equal to hideLE', () => { + const values = [5, 10, 15, 20, 25]; + const [min, max] = boundedMinMax(values, undefined, undefined, 10); + expect(min).toBe(15); + expect(max).toBe(25); + }); + + it('excludes all values when hideLE is higher than all values', () => { + const values = [5, 10, 15]; + const [min, max] = boundedMinMax(values, undefined, undefined, 20); + expect(min).toBe(Infinity); + expect(max).toBe(-Infinity); + }); + }); + + describe('with hideGE filter', () => { + it('excludes values greater than or equal to hideGE', () => { + const values = [5, 10, 15, 20, 25]; + const [min, max] = boundedMinMax(values, undefined, undefined, -Infinity, 20); + expect(min).toBe(5); + expect(max).toBe(15); + }); + + it('excludes all values when hideGE is lower than all values', () => { + const values = [15, 20, 25]; + const [min, max] = boundedMinMax(values, undefined, undefined, -Infinity, 10); + expect(min).toBe(Infinity); + expect(max).toBe(-Infinity); + }); + }); + + describe('with both hideLE and hideGE filters', () => { + it('excludes values outside the range', () => { + const values = [5, 10, 15, 20, 25, 30]; + const [min, max] = boundedMinMax(values, undefined, undefined, 10, 25); + expect(min).toBe(15); + expect(max).toBe(20); + }); + + it('works with provided min/max bounds', () => { + const values = [5, 10, 15, 20, 25, 30]; + const [min, max] = boundedMinMax(values, 0, 50, 10, 25); + expect(min).toBe(0); + expect(max).toBe(50); + }); + }); +}); + +describe('valuesToFills', () => { + // Fake color palette for testing index mapping + const palette5 = ['c0', 'c1', 'c2', 'c3', 'c4']; + + describe('basic mapping', () => { + it('maps values to palette indices', () => { + const values = [0, 25, 50, 75, 100]; + const fills = valuesToFills(values, palette5, 0, 100); + + expect(fills).toEqual([0, 1, 2, 3, 4]); + }); + + it('maps min value to first palette index', () => { + const values = [10]; + const fills = valuesToFills(values, palette5, 10, 20); + + expect(fills[0]).toBe(0); + }); + + it('maps max value to last palette index', () => { + const values = [20]; + const fills = valuesToFills(values, palette5, 10, 20); + + expect(fills[0]).toBe(4); + }); + + it('maps mid-range values proportionally', () => { + const values = [15]; + const fills = valuesToFills(values, palette5, 10, 20); + + // 15 is middle of 10-20, should map to index 2 (middle color) + expect(fills[0]).toBe(2); + }); + }); + + describe('edge cases', () => { + it('clamps values below min to first index', () => { + const values = [5, 8, 10]; + const fills = valuesToFills(values, palette5, 10, 20); + + expect(fills[0]).toBe(0); // 5 < 10 + expect(fills[1]).toBe(0); // 8 < 10 + }); + + it('clamps values above max to last index', () => { + const values = [20, 25, 30]; + const fills = valuesToFills(values, palette5, 10, 20); + + expect(fills[0]).toBe(4); // 20 = max + expect(fills[1]).toBe(4); // 25 > max + expect(fills[2]).toBe(4); // 30 > max + }); + + it('handles zero range (min equals max)', () => { + const values = [10, 10, 10]; + const fills = valuesToFills(values, palette5, 10, 10); + + // When range is 0, defaults to 1, so all values map to 0 + expect(fills).toEqual([0, 0, 0]); + }); + + it('handles single color palette', () => { + const values = [0, 50, 100]; + const palette = ['c0']; + const fills = valuesToFills(values, palette, 0, 100); + + expect(fills).toEqual([0, 0, 0]); + }); + + it('handles large palette', () => { + const values = [50]; + const palette = Array.from({ length: 256 }, (_, i) => `c${i}`); + const fills = valuesToFills(values, palette, 0, 100); + + // 50 is 50% of 0-100, should map to 128 (middle of 256) + expect(fills[0]).toBe(128); + }); + }); + + describe('negative values', () => { + it('handles negative min and max', () => { + const values = [-10, -5, 0]; + const palette = ['c0', 'c1', 'c2']; + const fills = valuesToFills(values, palette, -10, 0); + + expect(fills[0]).toBe(0); // -10 is min + expect(fills[1]).toBe(1); // -5 is middle + expect(fills[2]).toBe(2); // 0 is max + }); + + it('handles range crossing zero', () => { + const values = [-10, 0, 10]; + const palette = ['c0', 'c1', 'c2']; + const fills = valuesToFills(values, palette, -10, 10); + + expect(fills[0]).toBe(0); // -10 is min + expect(fills[1]).toBe(1); // 0 is middle + expect(fills[2]).toBe(2); // 10 is max + }); + }); + + describe('preserves array length', () => { + it('returns array with same length as input', () => { + const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + const palette = ['c0', 'c1']; + const fills = valuesToFills(values, palette, 1, 10); + + expect(fills.length).toBe(values.length); + }); + + it('handles empty array', () => { + const values: number[] = []; + const fills = valuesToFills(values, palette5, 0, 100); + + expect(fills).toEqual([]); + }); }); }); diff --git a/public/app/plugins/panel/heatmap/utils.ts b/public/app/plugins/panel/heatmap/utils.ts index a51c86f0d1c..6ae058f6261 100644 --- a/public/app/plugins/panel/heatmap/utils.ts +++ b/public/app/plugins/panel/heatmap/utils.ts @@ -14,13 +14,22 @@ import { } from '@grafana/data'; import { AxisPlacement, ScaleDirection, ScaleDistribution, ScaleOrientation, HeatmapCellLayout } from '@grafana/schema'; import { UPlotConfigBuilder, UPlotConfigPrepFn } from '@grafana/ui'; -import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap'; +import { + calculateBucketFactor, + isHeatmapCellsDense, + readHeatmapRowsCustomMeta, +} from 'app/features/transformers/calculateHeatmap/heatmap'; import { pointWithin, Quadtree, Rect } from '../barchart/quadtree'; import { HeatmapData } from './fields'; import { FieldConfig, HeatmapSelectionMode, YAxisConfig } from './types'; +/** Validates and returns a safe log base (2 or 10), defaults to 2 if invalid */ +export function toLogBase(value: number | undefined): 2 | 10 { + return value === 10 ? 10 : 2; +} + interface PathbuilderOpts { each: (u: uPlot, seriesIdx: number, dataIdx: number, lft: number, top: number, wid: number, hgt: number) => void; gap?: number | null; @@ -54,6 +63,7 @@ interface PrepConfigOpts { ySizeDivisor?: number; selectionMode?: HeatmapSelectionMode; xAxisConfig?: Parameters[0]['xAxisConfig']; + rowsFrame?: { yBucketScale?: { type: ScaleDistribution; log?: number; linearThreshold?: number } }; } export function prepConfig(opts: PrepConfigOpts) { @@ -69,8 +79,11 @@ export function prepConfig(opts: PrepConfigOpts) { ySizeDivisor, selectionMode = HeatmapSelectionMode.X, xAxisConfig, + rowsFrame, } = opts; + const yBucketScale = rowsFrame?.yBucketScale; + const xScaleKey = 'x'; let isTime = true; @@ -196,7 +209,20 @@ export function prepConfig(opts: PrepConfigOpts) { const yScale = yFieldConfig?.scaleDistribution ?? { type: ScaleDistribution.Linear }; const yAxisReverse = Boolean(yAxisConfig.reverse); const isSparseHeatmap = heatmapType === DataFrameType.HeatmapCells && !isHeatmapCellsDense(dataRef.current?.heatmap!); - const shouldUseLogScale = yScale.type !== ScaleDistribution.Linear || isSparseHeatmap; + + const scaleDistribution = (() => { + if (yBucketScale) { + return yBucketScale.type; + } + if (yScale.type !== ScaleDistribution.Linear || isSparseHeatmap) { + return ScaleDistribution.Log; + } + return ScaleDistribution.Linear; + })(); + + const scaleLog = toLogBase(yBucketScale?.log ?? yScale.log); + const scaleLinearThreshold = yBucketScale?.linearThreshold; + const isOrdinalY = readHeatmapRowsCustomMeta(dataRef.current?.heatmap).yOrdinalDisplay != null; // random to prevent syncing y in other heatmaps @@ -210,8 +236,9 @@ export function prepConfig(opts: PrepConfigOpts) { orientation: ScaleOrientation.Vertical, direction: yAxisReverse ? ScaleDirection.Down : ScaleDirection.Up, // should be tweakable manually - distribution: shouldUseLogScale ? ScaleDistribution.Log : ScaleDistribution.Linear, - log: yScale.log ?? 2, + distribution: scaleDistribution, + log: scaleLog, + linearThreshold: scaleLinearThreshold, range: // sparse already accounts for le/ge by explicit yMin & yMax cell bounds, so no need to expand y range isSparseHeatmap @@ -224,16 +251,16 @@ export function prepConfig(opts: PrepConfigOpts) { let scaleMin: number | null, scaleMax: number | null; - [scaleMin, scaleMax] = shouldUseLogScale - ? uPlot.rangeLog(dataMin, dataMax, (yScale.log ?? 2) as unknown as uPlot.Scale.LogBase, true) - : [dataMin, dataMax]; + const isLogScale = + scaleDistribution === ScaleDistribution.Log || scaleDistribution === ScaleDistribution.Symlog; + [scaleMin, scaleMax] = isLogScale ? uPlot.rangeLog(dataMin, dataMax, scaleLog, true) : [dataMin, dataMax]; - if (shouldUseLogScale && !isOrdinalY) { + let { min: explicitMin, max: explicitMax } = yAxisConfig; + + if (isLogScale && !isOrdinalY) { let yExp = u.scales[yScaleKey].log!; let log = yExp === 2 ? Math.log2 : Math.log10; - let { min: explicitMin, max: explicitMax } = yAxisConfig; - // guard against <= 0 if (explicitMin != null && explicitMin > 0) { // snap to magnitude @@ -245,6 +272,9 @@ export function prepConfig(opts: PrepConfigOpts) { let maxLog = log(explicitMax); scaleMax = yExp ** incrRoundUp(maxLog, 1); } + } else if (!isOrdinalY) { + // Apply explicit min/max for linear scale + [scaleMin, scaleMax] = applyExplicitMinMax(scaleMin, scaleMax, explicitMin, explicitMax); } return [scaleMin, scaleMax]; @@ -257,7 +287,7 @@ export function prepConfig(opts: PrepConfigOpts) { let { min: explicitMin, max: explicitMax } = yAxisConfig; // logarithmic expansion - if (shouldUseLogScale) { + if (scaleDistribution === ScaleDistribution.Log || scaleDistribution === ScaleDistribution.Symlog) { let yExp = u.scales[yScaleKey].log!; let minExpanded = false; @@ -280,17 +310,31 @@ export function prepConfig(opts: PrepConfigOpts) { } } + // For pre-bucketed data with explicit scale, calculate expansion factor from actual bucket spacing + // For calculated heatmaps, use the full log base + let expansionFactor: number = yExp; + + if (yBucketScale !== undefined) { + // Try to infer the bucket factor from the actual data spacing + const yValues = u.data[1]?.[1]; + if (Array.isArray(yValues) && yValues.length >= 2 && typeof yValues[0] === 'number') { + expansionFactor = calculateBucketFactor(yValues, yExp); + } + } + if (dataRef.current?.yLayout === HeatmapCellLayout.le) { if (!minExpanded) { - scaleMin /= yExp; + scaleMin /= expansionFactor; } } else if (dataRef.current?.yLayout === HeatmapCellLayout.ge) { if (!maxExpanded) { - scaleMax *= yExp; + scaleMax *= expansionFactor; } } else { - scaleMin /= yExp / 2; - scaleMax *= yExp / 2; + // Unknown layout - expand both directions + const factor = Math.sqrt(expansionFactor); // Use sqrt for balanced expansion + scaleMin /= factor; + scaleMax *= factor; } if (!isOrdinalY) { @@ -383,7 +427,7 @@ export function prepConfig(opts: PrepConfigOpts) { return splits.map((v) => v < 0 ? (meta.yMinDisplay ?? '') // Check prometheus style labels - : (meta.yOrdinalDisplay[v] ?? '') + : (meta.yOrdinalDisplay?.[v] ?? '') ); } return splits; @@ -585,15 +629,19 @@ export function heatmapPathsDense(opts: PathbuilderOpts) { let ySize: number; if (scaleX.distr === 3) { - xSize = Math.abs(valToPosX(xs[0] * scaleX.log!, scaleX, xDim, xOff) - valToPosX(xs[0], scaleX, xDim, xOff)); + // For log scales, calculate cell size from actual adjacent bucket positions + const nextXValue = xs[yBinQty] ?? xs[0] * scaleX.log!; + xSize = Math.abs(valToPosX(nextXValue, scaleX, xDim, xOff) - valToPosX(xs[0], scaleX, xDim, xOff)); } else { xSize = Math.abs(valToPosX(xBinIncr, scaleX, xDim, xOff) - valToPosX(0, scaleX, xDim, xOff)); } if (scaleY.distr === 3) { - ySize = - Math.abs(valToPosY(ys[0] * scaleY.log!, scaleY, yDim, yOff) - valToPosY(ys[0], scaleY, yDim, yOff)) / - ySizeDivisor; + // Use actual data spacing for pre-bucketed data, or full magnitude for calculated heatmaps with splits + const nextYValue = ySizeDivisor === 1 ? (ys[1] ?? ys[0] * scaleY.log!) : ys[0] * scaleY.log!; + + const baseYSize = Math.abs(valToPosY(nextYValue, scaleY, yDim, yOff) - valToPosY(ys[0], scaleY, yDim, yOff)); + ySize = baseYSize / ySizeDivisor; } else { ySize = Math.abs(valToPosY(yBinIncr, scaleY, yDim, yOff) - valToPosY(0, scaleY, yDim, yOff)) / ySizeDivisor; } @@ -882,3 +930,30 @@ export const valuesToFills = (values: number[], palette: string[], minValue: num return indexedFills; }; + +/** + * Calculates the Y-axis size divisor for heatmap cell rendering. + * For log/symlog scales with calculated data (no explicit scale), divides cells by the split value. + * Otherwise returns 1 (no division). + */ +export function calculateYSizeDivisor( + scaleType: ScaleDistribution | undefined, + hasExplicitScale: boolean, + splitValue: number | string | undefined +): number { + const isLogScale = scaleType === ScaleDistribution.Log || scaleType === ScaleDistribution.Symlog; + return isLogScale && !hasExplicitScale ? +(splitValue || 1) : 1; +} + +/** + * Applies explicit min/max values to scale range for linear scales. + * Returns the original values if explicitMin/explicitMax are undefined. + */ +export function applyExplicitMinMax( + scaleMin: number | null, + scaleMax: number | null, + explicitMin: number | undefined, + explicitMax: number | undefined +): [number | null, number | null] { + return [explicitMin ?? scaleMin, explicitMax ?? scaleMax]; +} diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 6949fa04bb3..0de5ac009db 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -9361,6 +9361,7 @@ "name-unit": "Unit", "name-value-name": "Value name", "name-y-axis-scale": "Y axis scale", + "name-y-bucket-scale": "Y bucket scale", "placeholder-axis-label": "Auto", "placeholder-axis-width": "Auto", "placeholder-decimals": "Auto", @@ -9390,6 +9391,18 @@ "label-all": "All", "label-hidden": "Hidden", "label-single": "Single" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "Range within which the scale is linear", + "linear-threshold-label": "Linear threshold", + "linear-threshold-placeholder": "1", + "log-base-label": "Log base", + "scale-options": { + "label-auto": "Auto", + "label-linear": "Linear", + "label-log": "Log", + "label-symlog": "Symlog" + } } }, "help-modal": { From 0ec716a433c3f4e1304e2ec7e111eaf068936b33 Mon Sep 17 00:00:00 2001 From: Nathan Marrs Date: Thu, 18 Dec 2025 15:01:16 -0800 Subject: [PATCH 242/297] Embedded Dashboard Panels: Add Grafana Branding (#115198) * feat: add Grafana logo to embedded panels - Add Grafana logo watermark to solo panel view (embedded panels) - Logo appears in top-right corner with subtle background container - Logo hides on hover to avoid interfering with panel content - Uses React state to track hover for reliable behavior across nested elements * minor formatting * update changes to match public dashboards styling * match styles of public dashboards * feat: add responsive Grafana branding to embedded panels - Add 'Powered by Grafana' branding with text logo to solo panel view - Implement responsive scaling based on panel dimensions (0.6x to 1.0x) - Logo and text scale proportionally with panel size - Branding hides on hover to avoid interfering with panel content - Matches public dashboard branding pattern for consistency - Uses ResizeObserver for efficient responsive updates * feat: add Grafana branding to embedded solo panels - Add 'Powered by Grafana' branding with text logo to embedded panels - Create SoloPanelPageLogo component for reusable branding - Implement responsive scaling based on panel dimensions - Add hover-to-hide functionality to avoid content overlap - Logo scales between 0.6x and 1.0x based on panel size * refactor: move scale calculation into SoloPanelPageLogo component - Move responsive scale calculation logic from SoloPanelRenderer to SoloPanelPageLogo - Logo component now manages its own scaling based on container dimensions - Improves separation of concerns and component encapsulation * feat: add hideLogo query parameter to disable embedded panel branding - Add hideLogo query parameter support to SoloPanelPage - Logo can be hidden via ?hideLogo, ?hideLogo=true, or ?hideLogo=1 - Useful for customers who want to disable branding and for image rendering scenarios - Update Props interface to include hideLogo in queryParams type * feat: hide logo in panel image renderer URLs - Add hideLogo=true parameter to image renderer URLs in ShareLinkTab - Ensures logo is hidden when generating panel images through share feature - Update test to expect hideLogo=true in render URL * feat: hide logo in old dashboard sharing panel image URLs - Add hideLogo=true parameter to buildImageUrl in ShareModal utils - Ensures logo is hidden when generating panel images through old share modal - Update all ShareLink tests to expect hideLogo=true in render URLs * test: add comprehensive tests for SoloPanelPage and SoloPanelPageLogo - Add SoloPanelPageLogo tests covering rendering, hover behavior, theme selection, and scaling - Add SoloPanelPage tests covering logo visibility based on hideLogo prop - Test logo hiding functionality (most important behavior) - Test responsive scaling based on container dimensions - Test ResizeObserver integration - All 14 tests passing * refactor: centralize hideLogo handling in SoloPanelPageLogo Move hideLogo parsing and decision-making into SoloPanelPageLogo so SoloPanelPage/SoloPanelRenderer only pass through the raw query param value. * chore: clean up solo logo test and share link params Remove a duplicate SVG mock in SoloPanelPageLogo.test, and simplify ShareLinkTab image URL building without changing behavior. * chore: revert ShareLinkTab image query refactor Restore the previous image URL query-param mutation logic in ShareLinkTab to reduce risk. * chore: set hideLogo once for ShareLinkTab image URLs Avoid passing hideLogo twice when building the rendered image URL. * fix: handle boolean hideLogo query param in SoloPanelPageLogo Handle query params that are represented as booleans (e.g., ?hideLogo) and arrays, and avoid calling trim() on non-strings. * fix i18n * fix(dashboard-scene): address SoloPanelPageLogo review feedback Avoid double-scaling logo margin, clarify scaling comments, and extend tests for null/array values and ResizeObserver cleanup. * update margin left on logo to better match text spacing --- .../sharing/ShareLinkTab.test.tsx | 2 +- .../dashboard-scene/sharing/ShareLinkTab.tsx | 3 + .../solo/SoloPanelPage.test.tsx | 152 +++++++++ .../dashboard-scene/solo/SoloPanelPage.tsx | 67 ++-- .../solo/SoloPanelPageLogo.test.tsx | 291 ++++++++++++++++++ .../solo/SoloPanelPageLogo.tsx | 159 ++++++++++ .../components/ShareModal/ShareLink.test.tsx | 10 +- .../dashboard/components/ShareModal/utils.ts | 1 + public/locales/en-US/grafana.json | 3 + 9 files changed, 663 insertions(+), 25 deletions(-) create mode 100644 public/app/features/dashboard-scene/solo/SoloPanelPage.test.tsx create mode 100644 public/app/features/dashboard-scene/solo/SoloPanelPageLogo.test.tsx create mode 100644 public/app/features/dashboard-scene/solo/SoloPanelPageLogo.tsx diff --git a/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx b/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx index d50de744de1..ffdf1edd5dc 100644 --- a/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareLinkTab.test.tsx @@ -89,7 +89,7 @@ describe('ShareLinkTab', () => { await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage }) ).toHaveAttribute( 'href', - 'http://dashboards.grafana.com/grafana/render/d-solo/dash-1?from=2019-02-11T13:00:00.000Z&to=2019-02-11T19:00:00.000Z&panelId=A$panel-12&__feature.dashboardSceneSolo=true&width=1000&height=500&tz=Pacific%2FEaster' + 'http://dashboards.grafana.com/grafana/render/d-solo/dash-1?from=2019-02-11T13:00:00.000Z&to=2019-02-11T19:00:00.000Z&panelId=A$panel-12&__feature.dashboardSceneSolo=true&hideLogo=true&width=1000&height=500&tz=Pacific%2FEaster' ); }); }); diff --git a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx index 9d2daa5812b..cd70f0e2e83 100644 --- a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx @@ -81,6 +81,9 @@ export class ShareLinkTab extends SceneObjectBase implements imageQueryParams['__feature.dashboardSceneSolo'] = true; } + // hide Grafana logo in the rendered image + urlParamsUpdate.hideLogo = 'true'; + const imageUrl = getDashboardUrl({ uid: dashboard.state.uid, currentQueryParams: window.location.search, diff --git a/public/app/features/dashboard-scene/solo/SoloPanelPage.test.tsx b/public/app/features/dashboard-scene/solo/SoloPanelPage.test.tsx new file mode 100644 index 00000000000..a570131e6f9 --- /dev/null +++ b/public/app/features/dashboard-scene/solo/SoloPanelPage.test.tsx @@ -0,0 +1,152 @@ +import { render, screen } from '@testing-library/react'; +import { useParams } from 'react-router-dom-v5-compat'; + +import { SceneTimeRange, VizPanel } from '@grafana/scenes'; + +import { getDashboardScenePageStateManager } from '../pages/DashboardScenePageStateManager'; +import { DashboardScene } from '../scene/DashboardScene'; +import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager'; + +import { SoloPanelRenderer } from './SoloPanelPage'; + +// Mock dependencies +jest.mock('react-router-dom-v5-compat', () => ({ + useParams: jest.fn(), +})); + +jest.mock('../pages/DashboardScenePageStateManager', () => ({ + getDashboardScenePageStateManager: jest.fn(), +})); + +jest.mock('../scene/SoloPanelContext', () => ({ + SoloPanelContextProvider: ({ children }: { children: React.ReactNode }) =>
{children}
, + useDefineSoloPanelContext: jest.fn(() => ({})), +})); + +jest.mock('./SoloPanelPageLogo', () => ({ + shouldHideSoloPanelLogo: (hideLogo?: unknown) => { + if (hideLogo === undefined) { + return false; + } + if (hideLogo === true) { + return true; + } + if (hideLogo === false) { + return false; + } + if (Array.isArray(hideLogo)) { + hideLogo = hideLogo[0] ?? ''; + } + const normalized = String(hideLogo).trim().toLowerCase(); + return normalized !== 'false' && normalized !== '0'; + }, + SoloPanelPageLogo: ({ isHovered, hideLogo }: { isHovered: boolean; hideLogo?: unknown }) => { + if (hideLogo === true) { + return null; + } + if (hideLogo === false) { + return ( +
+ Logo +
+ ); + } + if (Array.isArray(hideLogo)) { + hideLogo = hideLogo[0] ?? ''; + } + if (hideLogo !== undefined) { + const normalized = String(hideLogo).trim().toLowerCase(); + if (normalized !== 'false' && normalized !== '0') { + return null; + } + } + return ( +
+ Logo +
+ ); + }, +})); + +describe('SoloPanelPage', () => { + const mockStateManager = { + useState: jest.fn(() => ({ + dashboard: null, + loadError: null, + })), + loadDashboard: jest.fn(), + clearState: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + (getDashboardScenePageStateManager as jest.Mock).mockReturnValue(mockStateManager); + (useParams as jest.Mock).mockReturnValue({ uid: 'test-uid', type: undefined, slug: undefined }); + }); + + describe('SoloPanelRenderer', () => { + const createMockDashboard = () => { + const panel = new VizPanel({ + title: 'Test Panel', + pluginId: 'table', + key: 'panel-1', + }); + + const dashboard = new DashboardScene({ + title: 'Test Dashboard', + uid: 'test-dash', + $timeRange: new SceneTimeRange({}), + body: DefaultGridLayoutManager.fromVizPanels([panel]), + }); + + // Mock the activate method + dashboard.activate = jest.fn(() => jest.fn()); + + // Mock useState to return the dashboard state object with required properties + dashboard.useState = jest.fn(() => ({ + controls: { + useState: jest.fn(() => ({ + refreshPicker: { + activate: jest.fn(() => jest.fn()), + }, + })), + }, + body: { + Component: () =>
Panel Content
, + }, + })) as unknown as typeof dashboard.useState; + + return dashboard; + }; + + it('should render the panel', () => { + const dashboard = createMockDashboard(); + render(); + + // The panel should be rendered (we can't easily test the actual panel content without more setup) + expect(screen.getByTestId('solo-panel-logo')).toBeInTheDocument(); + }); + + it('should render logo when hideLogo is false', () => { + const dashboard = createMockDashboard(); + render(); + + expect(screen.getByTestId('solo-panel-logo')).toBeInTheDocument(); + }); + + it('should not render logo when hideLogo is true', () => { + const dashboard = createMockDashboard(); + render(); + + expect(screen.queryByTestId('solo-panel-logo')).not.toBeInTheDocument(); + }); + + it('should initialize with isHovered as false', () => { + const dashboard = createMockDashboard(); + render(); + + const logo = screen.getByTestId('solo-panel-logo'); + expect(logo).toHaveAttribute('data-hovered', 'false'); + }); + }); +}); diff --git a/public/app/features/dashboard-scene/solo/SoloPanelPage.tsx b/public/app/features/dashboard-scene/solo/SoloPanelPage.tsx index 306776911de..65b7fbb443d 100644 --- a/public/app/features/dashboard-scene/solo/SoloPanelPage.tsx +++ b/public/app/features/dashboard-scene/solo/SoloPanelPage.tsx @@ -1,9 +1,9 @@ // Libraries import { css } from '@emotion/css'; -import { useEffect } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useParams } from 'react-router-dom-v5-compat'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, UrlQueryValue } from '@grafana/data'; import { t } from '@grafana/i18n'; import { UrlSyncContextProvider } from '@grafana/scenes'; import { Alert, Box, useStyles2 } from '@grafana/ui'; @@ -17,7 +17,10 @@ import { getDashboardScenePageStateManager } from '../pages/DashboardScenePageSt import { DashboardScene } from '../scene/DashboardScene'; import { SoloPanelContextProvider, useDefineSoloPanelContext } from '../scene/SoloPanelContext'; -export interface Props extends GrafanaRouteComponentProps {} +import { SoloPanelPageLogo } from './SoloPanelPageLogo'; + +export interface Props + extends GrafanaRouteComponentProps {} /** * Used for iframe embedding and image rendering of single panels @@ -52,18 +55,28 @@ export function SoloPanelPage({ queryParams }: Props) { return ( - + ); } export default SoloPanelPage; -export function SoloPanelRenderer({ dashboard, panelId }: { dashboard: DashboardScene; panelId: string }) { +export function SoloPanelRenderer({ + dashboard, + panelId, + hideLogo, +}: { + dashboard: DashboardScene; + panelId: string; + hideLogo?: UrlQueryValue; +}) { const { controls, body } = dashboard.useState(); const refreshPicker = controls?.useState()?.refreshPicker; const styles = useStyles2(getStyles); const soloPanelContext = useDefineSoloPanelContext(panelId)!; + const [isHovered, setIsHovered] = useState(false); + const containerRef = useRef(null); useEffect(() => { const dashDeactivate = dashboard.activate(); @@ -76,11 +89,19 @@ export function SoloPanelRenderer({ dashboard, panelId }: { dashboard: Dashboard }, [dashboard, refreshPicker]); return ( -
+
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + {renderHiddenVariables(dashboard)} - - - +
+ + + +
); } @@ -107,15 +128,23 @@ function renderHiddenVariables(dashboard: DashboardScene) { ); } -const getStyles = (theme: GrafanaTheme2) => ({ - container: css({ - position: 'fixed', - bottom: 0, - right: 0, - margin: 0, - left: 0, - top: 0, +const getStyles = (theme: GrafanaTheme2) => { + const panelWrapper = css({ width: '100%', height: '100%', - }), -}); + }); + + return { + container: css({ + position: 'fixed', + bottom: 0, + right: 0, + margin: 0, + left: 0, + top: 0, + width: '100%', + height: '100%', + }), + panelWrapper, + }; +}; diff --git a/public/app/features/dashboard-scene/solo/SoloPanelPageLogo.test.tsx b/public/app/features/dashboard-scene/solo/SoloPanelPageLogo.test.tsx new file mode 100644 index 00000000000..bbfe0c5bcdd --- /dev/null +++ b/public/app/features/dashboard-scene/solo/SoloPanelPageLogo.test.tsx @@ -0,0 +1,291 @@ +import { render, screen } from '@testing-library/react'; +import { createRef } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; + +import { shouldHideSoloPanelLogo, SoloPanelPageLogo } from './SoloPanelPageLogo'; + +// Mock the theme hook +const mockUseTheme2 = jest.fn(); +const mockUseStyles2 = jest.fn((fn) => fn({} as GrafanaTheme2)); + +jest.mock('@grafana/ui', () => ({ + ...jest.requireActual('@grafana/ui'), + useTheme2: () => mockUseTheme2(), + useStyles2: (fn: (theme: GrafanaTheme2) => Record) => mockUseStyles2(fn), +})); + +// Mock the logo images for dark and light modes +jest.mock('img/grafana_text_logo_dark.svg', () => 'grafana-text-logo-dark.svg'); +jest.mock('img/grafana_text_logo_light.svg', () => 'grafana-text-logo-light.svg'); + +// Mock ResizeObserver +global.ResizeObserver = jest.fn().mockImplementation((callback) => { + return { + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), + // Helper to trigger resize + trigger: (width: number, height: number) => { + callback([{ contentRect: { width, height } }]); + }, + }; +}); + +// Helper function to assign a mock div to a ref +function assignMockDivToRef(ref: React.RefObject, mockDiv: HTMLDivElement) { + // Use type assertion to bypass readonly restriction in tests + (ref as { current: HTMLDivElement | null }).current = mockDiv; +} + +describe('SoloPanelPageLogo', () => { + describe('shouldHideSoloPanelLogo', () => { + it('treats null as false', () => { + expect(shouldHideSoloPanelLogo(null)).toBe(false); + }); + + it('treats presence (empty string) as true', () => { + expect(shouldHideSoloPanelLogo('')).toBe(true); + }); + + it('treats true/1 as true', () => { + expect(shouldHideSoloPanelLogo('true')).toBe(true); + expect(shouldHideSoloPanelLogo('1')).toBe(true); + expect(shouldHideSoloPanelLogo(' TRUE ')).toBe(true); + }); + + it('treats false/0 as false', () => { + expect(shouldHideSoloPanelLogo('false')).toBe(false); + expect(shouldHideSoloPanelLogo('0')).toBe(false); + expect(shouldHideSoloPanelLogo(' FALSE ')).toBe(false); + }); + + it('treats boolean true as true and boolean false as false', () => { + expect(shouldHideSoloPanelLogo(true)).toBe(true); + expect(shouldHideSoloPanelLogo(false)).toBe(false); + }); + + it('treats undefined as false', () => { + expect(shouldHideSoloPanelLogo(undefined)).toBe(false); + }); + + it('handles array values (uses the first value)', () => { + expect(shouldHideSoloPanelLogo([''])).toBe(true); + expect(shouldHideSoloPanelLogo(['true'])).toBe(true); + expect(shouldHideSoloPanelLogo(['1'])).toBe(true); + expect(shouldHideSoloPanelLogo(['false'])).toBe(false); + expect(shouldHideSoloPanelLogo(['0'])).toBe(false); + expect(shouldHideSoloPanelLogo(['false', 'true'])).toBe(false); + }); + }); + + const mockTheme = { + isDark: false, + colors: { + background: { primary: '#ffffff' }, + border: { weak: '#e0e0e0' }, + text: { secondary: '#666666' }, + }, + shape: { radius: { default: '4px' } }, + shadows: { z3: '0 2px 4px rgba(0,0,0,0.1)' }, + typography: { body: { fontSize: '14px' } }, + spacing: jest.fn((n: number) => `${n * 8}px`), + transitions: { + handleMotion: jest.fn(() => ({})), + }, + } as unknown as GrafanaTheme2; + + beforeEach(() => { + jest.clearAllMocks(); + mockUseTheme2.mockReturnValue({ + ...mockTheme, + isDark: false, + }); + mockUseStyles2.mockImplementation((fn) => fn(mockTheme)); + }); + + it('should render the logo component', () => { + const containerRef = createRef(); + const mockDiv = document.createElement('div'); + mockDiv.getBoundingClientRect = jest.fn(() => ({ + width: 800, + height: 600, + top: 0, + left: 0, + bottom: 600, + right: 800, + x: 0, + y: 0, + toJSON: jest.fn(), + })); + + assignMockDivToRef(containerRef, mockDiv); + + render(); + + expect(screen.getByText('Powered by')).toBeInTheDocument(); + expect(screen.getByAltText('Grafana')).toBeInTheDocument(); + }); + + it('should hide logo when isHovered is true', () => { + const containerRef = createRef(); + const mockDiv = document.createElement('div'); + mockDiv.getBoundingClientRect = jest.fn(() => ({ + width: 800, + height: 600, + top: 0, + left: 0, + bottom: 600, + right: 800, + x: 0, + y: 0, + toJSON: jest.fn(), + })); + assignMockDivToRef(containerRef, mockDiv); + + render(); + + // The logo should still be in the DOM but with reduced opacity + const poweredByText = screen.getByText('Powered by'); + expect(poweredByText).toBeInTheDocument(); + // The logoHidden class should be applied (we can't easily test the class name without more setup) + }); + + it('should show logo when isHovered is false', () => { + const containerRef = createRef(); + const mockDiv = document.createElement('div'); + mockDiv.getBoundingClientRect = jest.fn(() => ({ + width: 800, + height: 600, + top: 0, + left: 0, + bottom: 600, + right: 800, + x: 0, + y: 0, + toJSON: jest.fn(), + })); + assignMockDivToRef(containerRef, mockDiv); + + render(); + + // The logo should be visible + expect(screen.getByText('Powered by')).toBeInTheDocument(); + expect(screen.getByAltText('Grafana')).toBeInTheDocument(); + }); + + it('should use dark logo in dark theme', () => { + const containerRef = createRef(); + const mockDiv = document.createElement('div'); + mockDiv.getBoundingClientRect = jest.fn(() => ({ + width: 800, + height: 600, + top: 0, + left: 0, + bottom: 600, + right: 800, + x: 0, + y: 0, + toJSON: jest.fn(), + })); + assignMockDivToRef(containerRef, mockDiv); + + mockUseTheme2.mockReturnValue({ + ...mockTheme, + isDark: true, + }); + + render(); + + const logo = screen.getByAltText('Grafana'); + expect(logo).toHaveAttribute('src', 'grafana-text-logo-light.svg'); + }); + + it('should use correct logo based on theme', () => { + const containerRef = createRef(); + const mockDiv = document.createElement('div'); + mockDiv.getBoundingClientRect = jest.fn(() => ({ + width: 800, + height: 600, + top: 0, + left: 0, + bottom: 600, + right: 800, + x: 0, + y: 0, + toJSON: jest.fn(), + })); + assignMockDivToRef(containerRef, mockDiv); + + // The beforeEach sets isDark: false by default, so this should work + // But the previous test might have changed it, so let's ensure it's reset + mockUseTheme2.mockClear(); + mockUseTheme2.mockReturnValue({ + ...mockTheme, + isDark: false, + }); + + render(); + + const logo = screen.getByAltText('Grafana'); + // Verify logo is rendered (the exact src depends on theme, which is tested in other tests) + expect(logo).toBeInTheDocument(); + expect(logo).toHaveAttribute('src'); + }); + + it('should apply scaling styles based on container dimensions', () => { + const containerRef = createRef(); + const mockDiv = document.createElement('div'); + mockDiv.getBoundingClientRect = jest.fn(() => ({ + width: 400, + height: 300, + top: 0, + left: 0, + bottom: 300, + right: 400, + x: 0, + y: 0, + toJSON: jest.fn(), + })); + assignMockDivToRef(containerRef, mockDiv); + + render(); + + // Find the logo container by looking for the "Powered by" text's parent + const poweredByText = screen.getByText('Powered by'); + const logoContainer = poweredByText.parentElement as HTMLElement; + expect(logoContainer).toBeInTheDocument(); + // Check that inline styles are applied (scaling should be between 0.6 and 1.0) + expect(logoContainer.style.fontSize).toBeTruthy(); + expect(logoContainer.style.top).toBeTruthy(); + expect(logoContainer.style.right).toBeTruthy(); + }); + + it('should observe container resize', () => { + const containerRef = createRef(); + const mockDiv = document.createElement('div'); + mockDiv.getBoundingClientRect = jest.fn(() => ({ + width: 800, + height: 600, + top: 0, + left: 0, + bottom: 600, + right: 800, + x: 0, + y: 0, + toJSON: jest.fn(), + })); + assignMockDivToRef(containerRef, mockDiv); + + const { unmount } = render( + + ); + + expect(ResizeObserver).toHaveBeenCalled(); + const resizeObserverInstance = (ResizeObserver as jest.Mock).mock.results[0].value; + expect(resizeObserverInstance.observe).toHaveBeenCalledWith(mockDiv); + + unmount(); + expect(resizeObserverInstance.disconnect).toHaveBeenCalled(); + }); +}); diff --git a/public/app/features/dashboard-scene/solo/SoloPanelPageLogo.tsx b/public/app/features/dashboard-scene/solo/SoloPanelPageLogo.tsx new file mode 100644 index 00000000000..43889049807 --- /dev/null +++ b/public/app/features/dashboard-scene/solo/SoloPanelPageLogo.tsx @@ -0,0 +1,159 @@ +import { css, cx } from '@emotion/css'; +import { useEffect, useState } from 'react'; + +import { GrafanaTheme2, UrlQueryValue } from '@grafana/data'; +import { Trans } from '@grafana/i18n'; +import { useStyles2, useTheme2 } from '@grafana/ui'; +import grafanaTextLogoDarkSvg from 'img/grafana_text_logo_dark.svg'; +import grafanaTextLogoLightSvg from 'img/grafana_text_logo_light.svg'; + +interface SoloPanelPageLogoProps { + containerRef: React.RefObject; + isHovered: boolean; + hideLogo?: UrlQueryValue; +} + +export function shouldHideSoloPanelLogo(hideLogo?: UrlQueryValue): boolean { + if (hideLogo === undefined || hideLogo === null) { + return false; + } + + // React-router / locationSearchToObject can represent a "present but no value" query param as boolean true. + if (hideLogo === true) { + return true; + } + + if (hideLogo === false) { + return false; + } + + const value = Array.isArray(hideLogo) ? String(hideLogo[0] ?? '') : String(hideLogo); + + // Treat presence as "true", except explicit disable values. + // Examples: + // - ?hideLogo => hide + // - ?hideLogo=true => hide + // - ?hideLogo=1 => hide + // - ?hideLogo=false => show + // - ?hideLogo=0 => show + const normalized = value.trim().toLowerCase(); + return normalized !== 'false' && normalized !== '0'; +} + +export function SoloPanelPageLogo({ containerRef, isHovered, hideLogo }: SoloPanelPageLogoProps) { + const shouldHide = shouldHideSoloPanelLogo(hideLogo); + const [scale, setScale] = useState(1); + const styles = useStyles2(getStyles); + const theme = useTheme2(); + const grafanaLogo = theme.isDark ? grafanaTextLogoLightSvg : grafanaTextLogoDarkSvg; + + // Calculate responsive scale based on panel dimensions + useEffect(() => { + const updateScale = () => { + if (!containerRef.current) { + return; + } + + const { width, height } = containerRef.current.getBoundingClientRect(); + // Use the smaller dimension to ensure it scales appropriately for both wide and tall panels + const minDimension = Math.min(width, height); + + // Base scale calculation: scales from 0.6 (for small panels ~200px) up to 1.0 when the smaller dimension is ~800px + // Clamp to a maximum of 1.0 for larger panels + const baseScale = Math.max(0.6, Math.min(1.0, 0.6 + (minDimension - 200) / 600)); + + // Also consider width specifically for very wide but short panels; reaches 1.0 when width is ~1000px + const widthScale = Math.max(0.6, Math.min(1.0, 0.6 + (width - 200) / 800)); + + // Use the average of both for balanced scaling; panels around 1000x1000px (or larger in both dimensions) reach a scale of 1.0 + const finalScale = Math.min(1.0, (baseScale + widthScale) / 2); + setScale(finalScale); + }; + + updateScale(); + + const resizeObserver = new ResizeObserver(updateScale); + if (containerRef.current) { + resizeObserver.observe(containerRef.current); + } + + return () => { + resizeObserver.disconnect(); + }; + }, [containerRef]); + + if (shouldHide) { + return null; + } + + return ( +
+ + Powered by + + Grafana +
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => { + const logoContainer = css({ + position: 'absolute', + // top, right, and padding will be set via inline styles for scaling + backgroundColor: theme.colors.background.primary, + borderRadius: theme.shape.radius.default, + opacity: 0.9, + pointerEvents: 'none', + zIndex: 1000, + display: 'flex', + alignItems: 'center', + boxShadow: theme.shadows.z3, + border: `1px solid ${theme.colors.border.weak}`, + // Base font size - will be scaled via inline style + fontSize: theme.typography.body.fontSize, + lineHeight: 1.2, + [theme.transitions.handleMotion('no-preference', 'reduce')]: { + transition: 'opacity 0.2s ease-in-out', + }, + }); + + const logoHidden = css({ + opacity: 0, + }); + + const text = css({ + color: theme.colors.text.secondary, + // fontSize will be inherited from parent container's scale + lineHeight: 1.2, + display: 'block', + }); + + const logo = css({ + // height will be set via inline style (16px * scale) to scale with panel size + display: 'block', + flexShrink: 0, + }); + + return { + logoContainer, + logoHidden, + text, + logo, + }; +}; diff --git a/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx b/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx index eef8b965a98..8bc902b6d0d 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareLink.test.tsx @@ -106,7 +106,7 @@ describe('ShareModal', () => { render(); const base = 'http://dashboards.grafana.com/render/d-solo/abcdefghi/my-dash'; - const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&scale=1&tz=UTC'; + const params = '?from=1000&to=2000&orgId=1&panelId=22&hideLogo=true&width=1000&height=500&scale=1&tz=UTC'; expect( await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage }) ).toHaveAttribute('href', base + params); @@ -117,7 +117,7 @@ describe('ShareModal', () => { render(); const base = 'http://dashboards.grafana.com/render/dashboard-solo/script/my-dash.js'; - const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&scale=1&tz=UTC'; + const params = '?from=1000&to=2000&orgId=1&panelId=22&hideLogo=true&width=1000&height=500&scale=1&tz=UTC'; expect( await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage }) ).toHaveAttribute('href', base + params); @@ -154,7 +154,7 @@ describe('ShareModal', () => { await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage }) ).toHaveAttribute( 'href', - base + path + '?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&scale=1&tz=UTC' + base + path + '?from=1000&to=2000&orgId=1&panelId=1&hideLogo=true&width=1000&height=500&scale=1&tz=UTC' ); }); @@ -172,7 +172,7 @@ describe('ShareModal', () => { render(); const base = 'http://dashboards.grafana.com/render/d-solo/abcdefghi/my-dash'; - const params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&scale=1&tz=UTC'; + const params = '?from=1000&to=2000&orgId=1&panelId=22&hideLogo=true&width=1000&height=500&scale=1&tz=UTC'; expect( await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage }) ).toHaveAttribute('href', base + params); @@ -213,7 +213,7 @@ describe('when appUrl is set in the grafana config', () => { await screen.findByRole('link', { name: selectors.pages.SharePanelModal.linkToRenderedImage }) ).toHaveAttribute( 'href', - `http://dashboards.grafana.com/render/d-solo/${mockDashboard.uid}?orgId=1&from=1000&to=2000&panelId=${mockPanel.id}&width=1000&height=500&scale=1&tz=UTC` + `http://dashboards.grafana.com/render/d-solo/${mockDashboard.uid}?orgId=1&from=1000&to=2000&panelId=${mockPanel.id}&hideLogo=true&width=1000&height=500&scale=1&tz=UTC` ); }); }); diff --git a/public/app/features/dashboard/components/ShareModal/utils.ts b/public/app/features/dashboard/components/ShareModal/utils.ts index e60deafe3a5..c79203d5872 100644 --- a/public/app/features/dashboard/components/ShareModal/utils.ts +++ b/public/app/features/dashboard/components/ShareModal/utils.ts @@ -142,6 +142,7 @@ export function buildImageUrl( let imageUrl = soloUrl.replace(config.appSubUrl + '/dashboard-solo/', config.appSubUrl + '/render/dashboard-solo/'); imageUrl = imageUrl.replace(config.appSubUrl + '/d-solo/', config.appSubUrl + '/render/d-solo/'); imageUrl += + `&hideLogo=true` + `&width=${config.rendererDefaultImageWidth}` + `&height=${config.rendererDefaultImageHeight}` + `&scale=${config.rendererDefaultImageScale}` + diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 0de5ac009db..febd4c0442e 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -7166,6 +7166,9 @@ "time-range-label": "Lock time range" } }, + "embedded-panel": { + "powered-by": "Powered by" + }, "empty-list-cta": { "pro-tip": "ProTip: {{proTip}}" }, From 606a59584a5fce12fd48f9e4153e1e4b01cb7963 Mon Sep 17 00:00:00 2001 From: Collin Fingar Date: Thu, 18 Dec 2025 18:18:24 -0500 Subject: [PATCH 243/297] Saved Queries: Pass editor ref for dynamic dropdown display (#114321) * Saved Queries: Pass editor ref for dynamic dropdown display * Updated docs per feedback * Update docs/sources/visualizations/dashboards/build-dashboards/annotate-visualizations/index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/dashboards/build-dashboards/annotate-visualizations/index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/explore/get-started-with-explore.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> * Update docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> --------- Co-authored-by: Nathan Marrs Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> --- .../annotate-visualizations/index.md | 4 ++-- .../build-dashboards/create-dashboard/index.md | 4 ++-- .../explore/get-started-with-explore.md | 5 +++-- .../panel-editor-overview/index.md | 5 +++-- .../query-transform-data/_index.md | 10 +++++----- .../explore/QueryLibrary/QueryLibraryContext.tsx | 3 ++- .../query/components/QueryEditorRow.test.tsx | 3 +-- .../features/query/components/QueryEditorRow.tsx | 16 +++++++++++++--- 8 files changed, 31 insertions(+), 19 deletions(-) diff --git a/docs/sources/visualizations/dashboards/build-dashboards/annotate-visualizations/index.md b/docs/sources/visualizations/dashboards/build-dashboards/annotate-visualizations/index.md index f356d51c4a5..5458f90281e 100644 --- a/docs/sources/visualizations/dashboards/build-dashboards/annotate-visualizations/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/annotate-visualizations/index.md @@ -163,9 +163,9 @@ To add a new annotation query to a dashboard, follow these steps: 1. To create a query, do one of the following: - Write or construct a query in the query language of your data source. The annotation query options are different for each data source. For information about annotations in a specific data source, refer to the specific [data source](ref:data-source) topic. - - Click **Replace with saved query** to reuse a [saved query](ref:saved-queries). + - Open the **Saved queries** drop-down menu and click **Replace query** to reuse a [saved query](ref:saved-queries). -1. (Optional) To [save the query](ref:save-query) for reuse, click the **Save query** button (or icon). +1. (Optional) To [save the query](ref:save-query) for reuse, open the **Saved queries** drop-down menu and click the **Save query** option. 1. (Optional) Click **Test annotation query** to ensure that the query is working properly. 1. (Optional) To add subsequent queries, click **+ Add query** or **+ Add from saved queries**, and test them as many times as needed. diff --git a/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md b/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md index feb6573c3e9..3d5e765e0cd 100644 --- a/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md @@ -125,9 +125,9 @@ Dashboards and panels allow you to show your data in visual form. Each panel nee 1. To create a query, do one of the following: - Write or construct a query in the query language of your data source. - - Click **Replace with saved query** to reuse a [saved query](ref:saved-queries). + - Open the **Saved queries** drop-down menu and click **Replace query** to reuse a [saved query](ref:saved-queries). -1. (Optional) To [save the query](ref:save-query) for reuse, click the **Save query** button (or icon). +1. (Optional) To [save the query](ref:save-query) for reuse, open the **Saved queries** drop-down menu and click the **Save query** option. 1. Click **Refresh** to query the data source. 1. (Optional) To add subsequent queries, click **+ Add query** or **+ Add from saved queries**, and refresh the data source as many times as needed. diff --git a/docs/sources/visualizations/explore/get-started-with-explore.md b/docs/sources/visualizations/explore/get-started-with-explore.md index b59ac68c686..41f1f8c5c01 100644 --- a/docs/sources/visualizations/explore/get-started-with-explore.md +++ b/docs/sources/visualizations/explore/get-started-with-explore.md @@ -71,8 +71,9 @@ Explore consists of a toolbar, outline, query editor, the ability to add multipl - **Run query** - Click to run your query. - **Query editor** - Interface where you construct the query for a specific data source. Query editor elements differ based on data source. In order to run queries across multiple data sources you need to select **Mixed** from the data source picker. - - **Save query** - To [save the query](ref:save-query) for reuse, click the **Save query** button (or icon). - - **Replace with saved query** - Reuse a saved query. + - **Saved queries**: + - **Save query** - To [save the query](ref:save-query) for reuse, click the **Save query** button (or icon). + - **Replace query** - Reuse a saved query. - **+ Add query** - Add an additional query. - **+ Add from saved queries** - Add an additional query by reusing a saved query. diff --git a/docs/sources/visualizations/panels-visualizations/panel-editor-overview/index.md b/docs/sources/visualizations/panels-visualizations/panel-editor-overview/index.md index 4afbd8f71a6..f219f10a68a 100644 --- a/docs/sources/visualizations/panels-visualizations/panel-editor-overview/index.md +++ b/docs/sources/visualizations/panels-visualizations/panel-editor-overview/index.md @@ -88,8 +88,9 @@ The data section contains tabs where you enter queries, transform your data, and - **Queries** - Select your data source. You can also set or update the data source in existing dashboards using the drop-down menu in the **Queries** tab. - - **Save query** - To [save the query](ref:save-query) for reuse, click the **Save query** button (or icon). - - **Replace with saved query** - Reuse a saved query. + - **Saved queries**: + - **Save query** - To [save the query](ref:save-query) for reuse, click the **Save query** button (or icon). + - **Replace query** - Reuse a saved query. - **+ Add query** - Add an additional query. - **+ Add from saved queries** - Add an additional query by reusing a saved query. diff --git a/docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md b/docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md index 5b121028404..0aafe41f524 100644 --- a/docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md +++ b/docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md @@ -156,11 +156,11 @@ In the **Saved queries** drawer, you can: - Edit a query title, description, tags, or the availability of the query to other users in your organization. By default, saved queries are locked for editing. - When you access the **Saved queries** drawer from Explore, you can use the **Edit in Explore** option to edit the body of a query. -To access your saved queries, click **+ Add from saved queries** or **Replace with saved query** in the query editor: +To access your saved queries, click **+ Add from saved queries** or open the **Saved queries** drop-down menu and click **Replace query** in the query editor: {{< figure src="/media/docs/grafana/dashboards/screenshot-use-saved-queries-v12.3.png" max-width="750px" alt="Access saved queries" >}} -Clicking **+ Add from saved queries** adds an additional query, while clicking **Replace with saved query** updates your existing query. +Clicking **+ Add from saved queries** adds an additional query, while clicking **Replace query** in the **Saved queries** drop-down menu updates your existing query. {{< admonition type="note" >}} Users with Admin and Editor roles can create and save queries for reuse. @@ -172,7 +172,7 @@ Viewers can only reuse queries. To save a query you've created: -1. From the query editor, click the **Save query** icon: +1. From the query editor, open the **Saved queries** drop-down menu and click the **Save query** option: {{< figure src="/media/docs/grafana/panels-visualizations/screenshot-save-query-v12.2.png" max-width="750px" alt="Save a query" >}} @@ -227,7 +227,7 @@ To add a query, follow these steps: 1. To create a query, do one of the following: - Write or construct a query in the query language of your data source. - - Click **Replace with saved query** to reuse a saved query. + - Open the **Saved queries** drop-down menu and click **Replace query** to reuse a saved query. {{< admonition type="note" >}} [Saved queries](#saved-queries) is currently in [public preview](https://grafana.com/docs/release-life-cycle/). Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available. @@ -235,7 +235,7 @@ To add a query, follow these steps: This feature is only available on Grafana Enterprise and Grafana Cloud. {{< /admonition >}} -1. (Optional) To [save the query](#save-a-query) for reuse, click the **Save query** button (or icon). +1. (Optional) To [save the query](#save-a-query) for reuse, click the **Save query** option in the **Saved queries** drop-down menu. 1. (Optional) Click **+ Add query** or **Add from saved queries** to add more queries as needed. 1. Click **Run queries**. diff --git a/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx b/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx index 50ebe16e49c..6138829632a 100644 --- a/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx +++ b/public/app/features/explore/QueryLibrary/QueryLibraryContext.tsx @@ -43,7 +43,8 @@ export type QueryLibraryContextType = { app?: CoreApp, onUpdateSuccess?: () => void, onSelectQuery?: (query: DataQuery) => void, - datasourceFilters?: string[] + datasourceFilters?: string[], + parentRef?: React.RefObject ) => ReactNode; /** diff --git a/public/app/features/query/components/QueryEditorRow.test.tsx b/public/app/features/query/components/QueryEditorRow.test.tsx index f839e570819..13be75a8fcb 100644 --- a/public/app/features/query/components/QueryEditorRow.test.tsx +++ b/public/app/features/query/components/QueryEditorRow.test.tsx @@ -461,8 +461,7 @@ describe('QueryEditorRow', () => { render(); await waitFor(() => { - expect(screen.queryByText('Save query')).not.toBeInTheDocument(); - expect(screen.queryByText('Replace with saved query')).not.toBeInTheDocument(); + expect(screen.queryByText('Saved queries')).not.toBeInTheDocument(); }); }); diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx index b474d0293f9..5cc7f3467d3 100644 --- a/public/app/features/query/components/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -1,7 +1,7 @@ import classNames from 'classnames'; import { cloneDeep, filter, uniqBy, uniqueId } from 'lodash'; import pluralize from 'pluralize'; -import { PureComponent, ReactNode, type JSX } from 'react'; +import { PureComponent, ReactNode, type JSX, createRef } from 'react'; import { CoreApp, @@ -88,6 +88,7 @@ interface State { export class QueryEditorRow extends PureComponent, State> { dataSourceSrv = getDataSourceSrv(); id = ''; + editorRef = createRef(); state: State = { datasource: null, @@ -419,6 +420,7 @@ export class QueryEditorRow extends PureComponent )} @@ -542,7 +544,7 @@ export class QueryEditorRow extends PureComponent +
{queryLibraryRef && ( void; onSelectQuery: (query: DataQuery) => void; datasourceFilters: string[]; + parentRef: React.RefObject; }) { const { renderSavedQueryButtons } = useQueryLibraryContext(); - return renderSavedQueryButtons(props.query, props.app, props.onUpdateSuccess, props.onSelectQuery); + return renderSavedQueryButtons( + props.query, + props.app, + props.onUpdateSuccess, + props.onSelectQuery, + undefined, + props.parentRef + ); } // Will render editing header only if query library is enabled From 99f5f14de764bf2d09357fb81b0af82a39c7ddc0 Mon Sep 17 00:00:00 2001 From: Will Assis <35489495+gassiss@users.noreply.github.com> Date: Thu, 18 Dec 2025 18:35:32 -0500 Subject: [PATCH 244/297] unified-storage: move rvmanager into its own package (#115445) * unified-storage: move rvmanager into its own package so it can be reused with sqlkv later --- pkg/storage/unified/sql/backend.go | 9 +- pkg/storage/unified/sql/backend_test.go | 20 +++++ pkg/storage/unified/sql/bulk.go | 4 +- pkg/storage/unified/sql/list_iterator_test.go | 3 +- pkg/storage/unified/sql/queries.go | 72 +--------------- pkg/storage/unified/sql/queries_test.go | 23 ++--- .../data/resource_history_update_rv.sql | 0 .../data/resource_update_rv.sql | 0 .../data/resource_version_get.sql | 0 .../data/resource_version_insert.sql | 0 .../data/resource_version_update.sql | 0 pkg/storage/unified/sql/rvmanager/queries.go | 84 +++++++++++++++++++ .../unified/sql/{ => rvmanager}/rv_manager.go | 51 ++++++----- .../sql/{ => rvmanager}/rv_manager_test.go | 2 +- .../unified/sql/rvmanager/templates.go | 30 +++++++ 15 files changed, 186 insertions(+), 112 deletions(-) rename pkg/storage/unified/sql/{ => rvmanager}/data/resource_history_update_rv.sql (100%) rename pkg/storage/unified/sql/{ => rvmanager}/data/resource_update_rv.sql (100%) rename pkg/storage/unified/sql/{ => rvmanager}/data/resource_version_get.sql (100%) rename pkg/storage/unified/sql/{ => rvmanager}/data/resource_version_insert.sql (100%) rename pkg/storage/unified/sql/{ => rvmanager}/data/resource_version_update.sql (100%) create mode 100644 pkg/storage/unified/sql/rvmanager/queries.go rename pkg/storage/unified/sql/{ => rvmanager}/rv_manager.go (89%) rename pkg/storage/unified/sql/{ => rvmanager}/rv_manager_test.go (99%) create mode 100644 pkg/storage/unified/sql/rvmanager/templates.go diff --git a/pkg/storage/unified/sql/backend.go b/pkg/storage/unified/sql/backend.go index ebd04ce4a30..a129a01727a 100644 --- a/pkg/storage/unified/sql/backend.go +++ b/pkg/storage/unified/sql/backend.go @@ -31,6 +31,7 @@ import ( "github.com/grafana/grafana/pkg/storage/unified/resourcepb" "github.com/grafana/grafana/pkg/storage/unified/sql/db" "github.com/grafana/grafana/pkg/storage/unified/sql/dbutil" + "github.com/grafana/grafana/pkg/storage/unified/sql/rvmanager" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" "github.com/grafana/grafana/pkg/util/debouncer" ) @@ -126,7 +127,7 @@ type backend struct { notifier eventNotifier // resource version manager - rvManager *resourceVersionManager + rvManager *rvmanager.ResourceVersionManager // testing simulatedNetworkLatency time.Duration @@ -163,7 +164,7 @@ func (b *backend) initLocked(ctx context.Context) error { } // Initialize ResourceVersionManager - rvManager, err := NewResourceVersionManager(ResourceManagerOptions{ + rvManager, err := rvmanager.NewResourceVersionManager(rvmanager.ResourceManagerOptions{ Dialect: b.dialect, DB: b.db, }) @@ -928,12 +929,12 @@ func (b *backend) listLatestRVs(ctx context.Context) (groupResourceRV, error) { func (b *backend) fetchLatestRV(ctx context.Context, x db.ContextExecer, d sqltemplate.Dialect, group, resource string) (int64, error) { ctx, span := tracer.Start(ctx, "sql.backend.fetchLatestRV") defer span.End() - res, err := dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionGetRequest{ + res, err := dbutil.QueryRow(ctx, x, rvmanager.SqlResourceVersionGet, rvmanager.SqlResourceVersionGetRequest{ SQLTemplate: sqltemplate.New(d), Group: group, Resource: resource, ReadOnly: true, - Response: new(resourceVersionResponse), + Response: new(rvmanager.ResourceVersionResponse), }) if errors.Is(err, sql.ErrNoRows) { return 1, nil diff --git a/pkg/storage/unified/sql/backend_test.go b/pkg/storage/unified/sql/backend_test.go index f7ecd755f32..2f8f9fcb060 100644 --- a/pkg/storage/unified/sql/backend_test.go +++ b/pkg/storage/unified/sql/backend_test.go @@ -40,6 +40,26 @@ type testBackend struct { test.TestDBProvider } +func expectSuccessfulResourceVersionLock(t *testing.T, dbp test.TestDBProvider, rv int64, timestamp int64) { + dbp.SQLMock.ExpectQuery("select resource_version, unix_timestamp for update"). + WillReturnRows(sqlmock.NewRows([]string{"resource_version", "unix_timestamp"}). + AddRow(rv, timestamp)) +} + +func expectSuccessfulResourceVersionSaveRV(t *testing.T, dbp test.TestDBProvider) { + dbp.SQLMock.ExpectExec("update resource set resource_version").WillReturnResult(sqlmock.NewResult(1, 1)) + dbp.SQLMock.ExpectExec("update resource_history set resource_version").WillReturnResult(sqlmock.NewResult(1, 1)) + dbp.SQLMock.ExpectExec("update resource_version set resource_version").WillReturnResult(sqlmock.NewResult(1, 1)) +} + +func expectSuccessfulResourceVersionExec(t *testing.T, dbp test.TestDBProvider, cbs ...func()) { + for _, cb := range cbs { + cb() + } + expectSuccessfulResourceVersionLock(t, dbp, 100, 200) + expectSuccessfulResourceVersionSaveRV(t, dbp) +} + func (b testBackend) ExecWithResult(expectedSQL string, lastInsertID int64, rowsAffected int64) { b.SQLMock.ExpectExec(expectedSQL).WillReturnResult(sqlmock.NewResult(lastInsertID, rowsAffected)) } diff --git a/pkg/storage/unified/sql/bulk.go b/pkg/storage/unified/sql/bulk.go index 6580975a764..4037c74f0dd 100644 --- a/pkg/storage/unified/sql/bulk.go +++ b/pkg/storage/unified/sql/bulk.go @@ -281,13 +281,13 @@ func (b *backend) processBulkWithTx(ctx context.Context, tx db.Tx, setting resou } if b.dialect.DialectName() == "sqlite" { - nextRV, err := b.rvManager.lock(ctx, tx, key.Group, key.Resource) + nextRV, err := b.rvManager.Lock(ctx, tx, key.Group, key.Resource) if err != nil { b.log.Error("error locking RV", "error", err, "key", resource.NSGR(key)) } else { b.log.Info("successfully locked RV", "nextRV", nextRV, "key", resource.NSGR(key)) // Save the incremented RV - if err := b.rvManager.saveRV(ctx, tx, key.Group, key.Resource, nextRV); err != nil { + if err := b.rvManager.SaveRV(ctx, tx, key.Group, key.Resource, nextRV); err != nil { b.log.Error("error saving RV", "error", err, "key", resource.NSGR(key)) } else { b.log.Info("successfully saved RV", "rv", nextRV, "key", resource.NSGR(key)) diff --git a/pkg/storage/unified/sql/list_iterator_test.go b/pkg/storage/unified/sql/list_iterator_test.go index 3b567ca0f3e..e049e06525e 100644 --- a/pkg/storage/unified/sql/list_iterator_test.go +++ b/pkg/storage/unified/sql/list_iterator_test.go @@ -17,6 +17,7 @@ import ( dbsql "github.com/grafana/grafana/pkg/storage/unified/sql/db" "github.com/grafana/grafana/pkg/storage/unified/sql/db/dbimpl" "github.com/grafana/grafana/pkg/storage/unified/sql/dbutil" + "github.com/grafana/grafana/pkg/storage/unified/sql/rvmanager" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" "github.com/grafana/grafana/pkg/tests/testsuite" "github.com/grafana/grafana/pkg/util/testutil" @@ -94,7 +95,7 @@ func TestIntegrationListIter(t *testing.T) { return fmt.Errorf("failed to insert test data: %w", err) } - if _, err = dbutil.Exec(ctx, tx, sqlResourceUpdateRV, sqlResourceUpdateRVRequest{ + if _, err = dbutil.Exec(ctx, tx, rvmanager.SqlResourceUpdateRV, rvmanager.SqlResourceUpdateRVRequest{ SQLTemplate: sqltemplate.New(dialect), GUIDToRV: map[string]int64{ item.guid: item.resourceVersion, diff --git a/pkg/storage/unified/sql/queries.go b/pkg/storage/unified/sql/queries.go index 5b9a177e17c..725c4617403 100644 --- a/pkg/storage/unified/sql/queries.go +++ b/pkg/storage/unified/sql/queries.go @@ -38,10 +38,8 @@ var ( sqlResourceList = mustTemplate("resource_list.sql") sqlResourceHistoryList = mustTemplate("resource_history_list.sql") sqlResourceHistoryListModifiedSince = mustTemplate("resource_history_list_since_modified.sql") - sqlResourceUpdateRV = mustTemplate("resource_update_rv.sql") sqlResourceHistoryRead = mustTemplate("resource_history_read.sql") sqlResourceHistoryReadLatestRV = mustTemplate("resource_history_read_latest_rv.sql") - sqlResourceHistoryUpdateRV = mustTemplate("resource_history_update_rv.sql") sqlResourceHistoryInsert = mustTemplate("resource_history_insert.sql") sqlResourceHistoryPoll = mustTemplate("resource_history_poll.sql") sqlResourceHistoryGet = mustTemplate("resource_history_get.sql") @@ -51,10 +49,7 @@ var ( sqlResourceInsertFromHistory = mustTemplate("resource_insert_from_history.sql") // sqlResourceLabelsInsert = mustTemplate("resource_labels_insert.sql") - sqlResourceVersionGet = mustTemplate("resource_version_get.sql") - sqlResourceVersionUpdate = mustTemplate("resource_version_update.sql") - sqlResourceVersionInsert = mustTemplate("resource_version_insert.sql") - sqlResourceVersionList = mustTemplate("resource_version_list.sql") + sqlResourceVersionList = mustTemplate("resource_version_list.sql") sqlResourceBlobInsert = mustTemplate("resource_blob_insert.sql") sqlResourceBlobQuery = mustTemplate("resource_blob_query.sql") @@ -365,76 +360,11 @@ func (r sqlResourceBlobQueryRequest) Validate() error { return nil } -// update RV - -type sqlResourceUpdateRVRequest struct { - sqltemplate.SQLTemplate - GUIDToRV map[string]int64 - GUIDToSnowflakeRV map[string]int64 -} - -func (r sqlResourceUpdateRVRequest) Validate() error { - return nil // TODO -} - -func (r sqlResourceUpdateRVRequest) SlashFunc() string { - if r.DialectName() == "postgres" { - return "CHR(47)" - } - - return "CHAR(47)" -} - -func (r sqlResourceUpdateRVRequest) TildeFunc() string { - if r.DialectName() == "postgres" { - return "CHR(126)" - } - - return "CHAR(126)" -} - -// resource_version table requests. -type resourceVersionResponse struct { - ResourceVersion int64 - CurrentEpoch int64 -} - -func (r *resourceVersionResponse) Results() (*resourceVersionResponse, error) { - return r, nil -} - type groupResourceVersion struct { Group, Resource string ResourceVersion int64 } -type sqlResourceVersionUpsertRequest struct { - sqltemplate.SQLTemplate - Group, Resource string - ResourceVersion int64 -} - -func (r sqlResourceVersionUpsertRequest) Validate() error { - return nil // TODO -} - -type sqlResourceVersionGetRequest struct { - sqltemplate.SQLTemplate - Group, Resource string - ReadOnly bool - Response *resourceVersionResponse -} - -func (r sqlResourceVersionGetRequest) Validate() error { - return nil // TODO -} -func (r sqlResourceVersionGetRequest) Results() (*resourceVersionResponse, error) { - return &resourceVersionResponse{ - ResourceVersion: r.Response.ResourceVersion, - CurrentEpoch: r.Response.CurrentEpoch, - }, nil -} - type sqlResourceVersionListRequest struct { sqltemplate.SQLTemplate *groupResourceVersion diff --git a/pkg/storage/unified/sql/queries_test.go b/pkg/storage/unified/sql/queries_test.go index 5673bfad1e8..93e9b1f5af4 100644 --- a/pkg/storage/unified/sql/queries_test.go +++ b/pkg/storage/unified/sql/queries_test.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" + "github.com/grafana/grafana/pkg/storage/unified/sql/rvmanager" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate/mocks" ) @@ -162,10 +163,10 @@ func TestUnifiedStorageQueries(t *testing.T) { }, }, - sqlResourceUpdateRV: { + rvmanager.SqlResourceUpdateRV: { { Name: "single path", - Data: &sqlResourceUpdateRVRequest{ + Data: &rvmanager.SqlResourceUpdateRVRequest{ SQLTemplate: mocks.NewTestingSQLTemplate(), GUIDToRV: map[string]int64{ "guid1": 123, @@ -228,10 +229,10 @@ func TestUnifiedStorageQueries(t *testing.T) { }, }, - sqlResourceHistoryUpdateRV: { + rvmanager.SqlResourceHistoryUpdateRV: { { Name: "single path", - Data: &sqlResourceUpdateRVRequest{ + Data: &rvmanager.SqlResourceUpdateRVRequest{ SQLTemplate: mocks.NewTestingSQLTemplate(), GUIDToRV: map[string]int64{ "guid1": 123, @@ -334,23 +335,23 @@ func TestUnifiedStorageQueries(t *testing.T) { }, }, - sqlResourceVersionGet: { + rvmanager.SqlResourceVersionGet: { { Name: "single path", - Data: &sqlResourceVersionGetRequest{ + Data: &rvmanager.SqlResourceVersionGetRequest{ SQLTemplate: mocks.NewTestingSQLTemplate(), Resource: "resource", Group: "group", - Response: new(resourceVersionResponse), + Response: new(rvmanager.ResourceVersionResponse), ReadOnly: false, }, }, }, - sqlResourceVersionUpdate: { + rvmanager.SqlResourceVersionUpdate: { { Name: "increment resource version", - Data: &sqlResourceVersionUpsertRequest{ + Data: &rvmanager.SqlResourceVersionUpsertRequest{ SQLTemplate: mocks.NewTestingSQLTemplate(), Resource: "resource", Group: "group", @@ -359,10 +360,10 @@ func TestUnifiedStorageQueries(t *testing.T) { }, }, - sqlResourceVersionInsert: { + rvmanager.SqlResourceVersionInsert: { { Name: "single path", - Data: &sqlResourceVersionUpsertRequest{ + Data: &rvmanager.SqlResourceVersionUpsertRequest{ SQLTemplate: mocks.NewTestingSQLTemplate(), ResourceVersion: int64(12354), }, diff --git a/pkg/storage/unified/sql/data/resource_history_update_rv.sql b/pkg/storage/unified/sql/rvmanager/data/resource_history_update_rv.sql similarity index 100% rename from pkg/storage/unified/sql/data/resource_history_update_rv.sql rename to pkg/storage/unified/sql/rvmanager/data/resource_history_update_rv.sql diff --git a/pkg/storage/unified/sql/data/resource_update_rv.sql b/pkg/storage/unified/sql/rvmanager/data/resource_update_rv.sql similarity index 100% rename from pkg/storage/unified/sql/data/resource_update_rv.sql rename to pkg/storage/unified/sql/rvmanager/data/resource_update_rv.sql diff --git a/pkg/storage/unified/sql/data/resource_version_get.sql b/pkg/storage/unified/sql/rvmanager/data/resource_version_get.sql similarity index 100% rename from pkg/storage/unified/sql/data/resource_version_get.sql rename to pkg/storage/unified/sql/rvmanager/data/resource_version_get.sql diff --git a/pkg/storage/unified/sql/data/resource_version_insert.sql b/pkg/storage/unified/sql/rvmanager/data/resource_version_insert.sql similarity index 100% rename from pkg/storage/unified/sql/data/resource_version_insert.sql rename to pkg/storage/unified/sql/rvmanager/data/resource_version_insert.sql diff --git a/pkg/storage/unified/sql/data/resource_version_update.sql b/pkg/storage/unified/sql/rvmanager/data/resource_version_update.sql similarity index 100% rename from pkg/storage/unified/sql/data/resource_version_update.sql rename to pkg/storage/unified/sql/rvmanager/data/resource_version_update.sql diff --git a/pkg/storage/unified/sql/rvmanager/queries.go b/pkg/storage/unified/sql/rvmanager/queries.go new file mode 100644 index 00000000000..c0411cdf704 --- /dev/null +++ b/pkg/storage/unified/sql/rvmanager/queries.go @@ -0,0 +1,84 @@ +package rvmanager + +import ( + "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" +) + +type SqlResourceUpdateRVRequest struct { + sqltemplate.SQLTemplate + GUIDToRV map[string]int64 + GUIDToSnowflakeRV map[string]int64 +} + +func (r SqlResourceUpdateRVRequest) Validate() error { + return nil // TODO +} + +func (r SqlResourceUpdateRVRequest) SlashFunc() string { + if r.DialectName() == "postgres" { + return "CHR(47)" + } + + return "CHAR(47)" +} + +func (r SqlResourceUpdateRVRequest) TildeFunc() string { + if r.DialectName() == "postgres" { + return "CHR(126)" + } + + return "CHAR(126)" +} + +type ResourceVersionResponse struct { + ResourceVersion int64 + CurrentEpoch int64 +} + +func (r *ResourceVersionResponse) Results() (*ResourceVersionResponse, error) { + return r, nil +} + +type sqlResourceVersionGetRequest struct { + sqltemplate.SQLTemplate + Group, Resource string + ReadOnly bool + Response *ResourceVersionResponse +} + +func (r sqlResourceVersionGetRequest) Validate() error { + return nil // TODO +} +func (r sqlResourceVersionGetRequest) Results() (*ResourceVersionResponse, error) { + return &ResourceVersionResponse{ + ResourceVersion: r.Response.ResourceVersion, + CurrentEpoch: r.Response.CurrentEpoch, + }, nil +} + +type SqlResourceVersionUpsertRequest struct { + sqltemplate.SQLTemplate + Group, Resource string + ResourceVersion int64 +} + +func (r SqlResourceVersionUpsertRequest) Validate() error { + return nil // TODO +} + +type SqlResourceVersionGetRequest struct { + sqltemplate.SQLTemplate + Group, Resource string + ReadOnly bool + Response *ResourceVersionResponse +} + +func (r SqlResourceVersionGetRequest) Validate() error { + return nil // TODO +} +func (r SqlResourceVersionGetRequest) Results() (*ResourceVersionResponse, error) { + return &ResourceVersionResponse{ + ResourceVersion: r.Response.ResourceVersion, + CurrentEpoch: r.Response.CurrentEpoch, + }, nil +} diff --git a/pkg/storage/unified/sql/rv_manager.go b/pkg/storage/unified/sql/rvmanager/rv_manager.go similarity index 89% rename from pkg/storage/unified/sql/rv_manager.go rename to pkg/storage/unified/sql/rvmanager/rv_manager.go index 858345b1fc2..b4f3b5de596 100644 --- a/pkg/storage/unified/sql/rv_manager.go +++ b/pkg/storage/unified/sql/rvmanager/rv_manager.go @@ -1,4 +1,4 @@ -package sql +package rvmanager import ( "context" @@ -11,6 +11,7 @@ import ( "github.com/bwmarrin/snowflake" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -20,6 +21,8 @@ import ( "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" ) +var tracer = otel.Tracer("github.com/grafana/grafana/pkg/storage/unified/sql/rvmanager") + var ( rvmWriteDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ Name: "rvmanager_write_duration_seconds", @@ -62,8 +65,8 @@ const ( defaultBatchTimeout = 5 * time.Second ) -// resourceVersionManager handles resource version operations -type resourceVersionManager struct { +// ResourceVersionManager handles resource version operations +type ResourceVersionManager struct { dialect sqltemplate.Dialect db db.DB batchMu sync.RWMutex @@ -100,7 +103,7 @@ type ResourceManagerOptions struct { } // NewResourceVersionManager creates a new ResourceVersionManager -func NewResourceVersionManager(opts ResourceManagerOptions) (*resourceVersionManager, error) { +func NewResourceVersionManager(opts ResourceManagerOptions) (*ResourceVersionManager, error) { if opts.MaxBatchSize == 0 { opts.MaxBatchSize = defaultMaxBatchSize } @@ -113,7 +116,7 @@ func NewResourceVersionManager(opts ResourceManagerOptions) (*resourceVersionMan if opts.DB == nil { return nil, errors.New("db is required") } - return &resourceVersionManager{ + return &ResourceVersionManager{ dialect: opts.Dialect, db: opts.DB, batchChMap: make(map[string]chan *writeOp), @@ -123,7 +126,7 @@ func NewResourceVersionManager(opts ResourceManagerOptions) (*resourceVersionMan } // ExecWithRV executes the given function with an incremented resource version -func (m *resourceVersionManager) ExecWithRV(ctx context.Context, key *resourcepb.ResourceKey, fn WriteEventFunc) (rv int64, err error) { +func (m *ResourceVersionManager) ExecWithRV(ctx context.Context, key *resourcepb.ResourceKey, fn WriteEventFunc) (rv int64, err error) { rvmInflightWrites.WithLabelValues(key.Group, key.Resource).Inc() defer rvmInflightWrites.WithLabelValues(key.Group, key.Resource).Dec() @@ -179,7 +182,7 @@ func (m *resourceVersionManager) ExecWithRV(ctx context.Context, key *resourcepb } // startBatchProcessor is responsible for processing batches of write operations -func (m *resourceVersionManager) startBatchProcessor(group, resource string) { +func (m *ResourceVersionManager) startBatchProcessor(group, resource string) { ctx := context.TODO() batchKey := fmt.Sprintf("%s/%s", group, resource) @@ -216,7 +219,11 @@ func (m *resourceVersionManager) startBatchProcessor(group, resource string) { } } -func (m *resourceVersionManager) execBatch(ctx context.Context, group, resource string, batch []writeOp) { +var readCommitted = &sql.TxOptions{ + Isolation: sql.LevelReadCommitted, +} + +func (m *ResourceVersionManager) execBatch(ctx context.Context, group, resource string, batch []writeOp) { ctx, span := tracer.Start(ctx, "sql.resourceVersionManager.execBatch") defer span.End() @@ -245,7 +252,7 @@ func (m *resourceVersionManager) execBatch(ctx context.Context, group, resource guids := make([]string, len(batch)) // The GUIDs of the created resources in the same order as the batch rvs := make([]int64, len(batch)) // The RVs of the created resources in the same order as the batch - err = m.db.WithTx(ctx, ReadCommitted, func(ctx context.Context, tx db.Tx) error { + err = m.db.WithTx(ctx, readCommitted, func(ctx context.Context, tx db.Tx) error { span.AddEvent("starting_batch_transaction") writeTimer := prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) { @@ -268,7 +275,7 @@ func (m *resourceVersionManager) execBatch(ctx context.Context, group, resource lockTimer := prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) { rvmExecBatchPhaseDuration.WithLabelValues(group, resource, "waiting_for_lock").Observe(v) })) - rv, err := m.lock(ctx, tx, group, resource) + rv, err := m.Lock(ctx, tx, group, resource) lockTimer.ObserveDuration() if err != nil { span.AddEvent("resource_version_lock_failed", trace.WithAttributes( @@ -292,7 +299,7 @@ func (m *resourceVersionManager) execBatch(ctx context.Context, group, resource rv++ } // Update the resource version for the created resources in both the resource and the resource history - if _, err := dbutil.Exec(ctx, tx, sqlResourceUpdateRV, sqlResourceUpdateRVRequest{ + if _, err := dbutil.Exec(ctx, tx, SqlResourceUpdateRV, SqlResourceUpdateRVRequest{ SQLTemplate: sqltemplate.New(m.dialect), GUIDToRV: guidToRV, }); err != nil { @@ -303,7 +310,7 @@ func (m *resourceVersionManager) execBatch(ctx context.Context, group, resource } span.AddEvent("resource_versions_updated") - if _, err := dbutil.Exec(ctx, tx, sqlResourceHistoryUpdateRV, sqlResourceUpdateRVRequest{ + if _, err := dbutil.Exec(ctx, tx, SqlResourceHistoryUpdateRV, SqlResourceUpdateRVRequest{ SQLTemplate: sqltemplate.New(m.dialect), GUIDToRV: guidToRV, GUIDToSnowflakeRV: guidToSnowflakeRV, @@ -316,7 +323,7 @@ func (m *resourceVersionManager) execBatch(ctx context.Context, group, resource span.AddEvent("resource_history_versions_updated") // Record the latest RV in the resource version table - err = m.saveRV(ctx, tx, group, resource, rv) + err = m.SaveRV(ctx, tx, group, resource, rv) if err != nil { span.AddEvent("save_rv_failed", trace.WithAttributes( attribute.String("error", err.Error()), @@ -350,20 +357,20 @@ func snowflakeFromRv(rv int64) int64 { return (((rv / 1000) - snowflake.Epoch) << (snowflake.NodeBits + snowflake.StepBits)) + (rv % 1000) } -// lock locks the resource version for the given key -func (m *resourceVersionManager) lock(ctx context.Context, x db.ContextExecer, group, resource string) (nextRV int64, err error) { +// Lock locks the resource version for the given key +func (m *ResourceVersionManager) Lock(ctx context.Context, x db.ContextExecer, group, resource string) (nextRV int64, err error) { // 1. Lock the row and prevent concurrent updates until the transaction is committed - res, err := dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionGetRequest{ + res, err := dbutil.QueryRow(ctx, x, SqlResourceVersionGet, sqlResourceVersionGetRequest{ SQLTemplate: sqltemplate.New(m.dialect), Group: group, Resource: resource, - Response: new(resourceVersionResponse), + Response: new(ResourceVersionResponse), ReadOnly: false, // Lock the row for update }) if errors.Is(err, sql.ErrNoRows) { // If there wasn't a row for this resource, create it - if _, err = dbutil.Exec(ctx, x, sqlResourceVersionInsert, sqlResourceVersionUpsertRequest{ + if _, err = dbutil.Exec(ctx, x, SqlResourceVersionInsert, SqlResourceVersionUpsertRequest{ SQLTemplate: sqltemplate.New(m.dialect), Group: group, Resource: resource, @@ -372,11 +379,11 @@ func (m *resourceVersionManager) lock(ctx context.Context, x db.ContextExecer, g } // Fetch the newly created resource version - res, err = dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionGetRequest{ + res, err = dbutil.QueryRow(ctx, x, SqlResourceVersionGet, sqlResourceVersionGetRequest{ SQLTemplate: sqltemplate.New(m.dialect), Group: group, Resource: resource, - Response: new(resourceVersionResponse), + Response: new(ResourceVersionResponse), ReadOnly: true, }) if err != nil { @@ -390,8 +397,8 @@ func (m *resourceVersionManager) lock(ctx context.Context, x db.ContextExecer, g return max(res.CurrentEpoch, res.ResourceVersion+1), nil } -func (m *resourceVersionManager) saveRV(ctx context.Context, x db.ContextExecer, group, resource string, rv int64) error { - _, err := dbutil.Exec(ctx, x, sqlResourceVersionUpdate, sqlResourceVersionUpsertRequest{ +func (m *ResourceVersionManager) SaveRV(ctx context.Context, x db.ContextExecer, group, resource string, rv int64) error { + _, err := dbutil.Exec(ctx, x, SqlResourceVersionUpdate, SqlResourceVersionUpsertRequest{ SQLTemplate: sqltemplate.New(m.dialect), Group: group, Resource: resource, diff --git a/pkg/storage/unified/sql/rv_manager_test.go b/pkg/storage/unified/sql/rvmanager/rv_manager_test.go similarity index 99% rename from pkg/storage/unified/sql/rv_manager_test.go rename to pkg/storage/unified/sql/rvmanager/rv_manager_test.go index 46d23d26a59..9a2e105aa2f 100644 --- a/pkg/storage/unified/sql/rv_manager_test.go +++ b/pkg/storage/unified/sql/rvmanager/rv_manager_test.go @@ -1,4 +1,4 @@ -package sql +package rvmanager import ( "testing" diff --git a/pkg/storage/unified/sql/rvmanager/templates.go b/pkg/storage/unified/sql/rvmanager/templates.go new file mode 100644 index 00000000000..65c91a273af --- /dev/null +++ b/pkg/storage/unified/sql/rvmanager/templates.go @@ -0,0 +1,30 @@ +package rvmanager + +import ( + "embed" + "fmt" + "text/template" +) + +// Templates setup. +var ( + //go:embed data/*.sql + sqlTemplatesFS embed.FS + + sqlTemplates = template.Must(template.New("sql").ParseFS(sqlTemplatesFS, `data/*.sql`)) +) + +func mustTemplate(filename string) *template.Template { + if t := sqlTemplates.Lookup(filename); t != nil { + return t + } + panic(fmt.Sprintf("template file not found: %s", filename)) +} + +var ( + SqlResourceUpdateRV = mustTemplate("resource_update_rv.sql") + SqlResourceHistoryUpdateRV = mustTemplate("resource_history_update_rv.sql") + SqlResourceVersionGet = mustTemplate("resource_version_get.sql") + SqlResourceVersionUpdate = mustTemplate("resource_version_update.sql") + SqlResourceVersionInsert = mustTemplate("resource_version_insert.sql") +) From 7360194ab9ff58235c0817a6fc7d7ffa4fd97c09 Mon Sep 17 00:00:00 2001 From: Tania <10127682+undef1nd@users.noreply.github.com> Date: Fri, 19 Dec 2025 09:55:47 +0100 Subject: [PATCH 245/297] Chore: Remove `unifiedReqeustLog` feature flag (#115559) Chore: Remove unifiedReqeustLog feature flag --- .../configure-grafana/feature-toggles/index.md | 1 - packages/grafana-data/src/types/featureToggles.gen.ts | 5 ----- pkg/middleware/loggermw/logger.go | 5 +---- pkg/services/featuremgmt/registry.go | 7 ------- pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.go | 4 ---- pkg/services/featuremgmt/toggles_gen.json | 3 ++- 7 files changed, 3 insertions(+), 23 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index b80fa49f815..16c97f6d10a 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -31,7 +31,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `logsContextDatasourceUi` | Allow datasource to provide custom UI for context view | Yes | | `lokiQuerySplitting` | Split large interval queries into subqueries with smaller time intervals | Yes | | `influxdbBackendMigration` | Query InfluxDB InfluxQL without the proxy | Yes | -| `unifiedRequestLog` | Writes error logs to the request logger | Yes | | `logsExploreTableVisualisation` | A table visualisation for logs in Explore | Yes | | `awsDatasourcesTempCredentials` | Support temporary security credentials in AWS plugins for Grafana Cloud customers | Yes | | `awsAsyncQueryCaching` | Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled | Yes | diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 199be6d9c3f..ea9f1c22f39 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -126,11 +126,6 @@ export interface FeatureToggles { */ disableSSEDataplane?: boolean; /** - * Writes error logs to the request logger - * @default true - */ - unifiedRequestLog?: boolean; - /** * Uses JWT-based auth for rendering instead of relying on remote cache */ renderAuthJWT?: boolean; diff --git a/pkg/middleware/loggermw/logger.go b/pkg/middleware/loggermw/logger.go index 1255d87aa0e..1b4f3d4d2d4 100644 --- a/pkg/middleware/loggermw/logger.go +++ b/pkg/middleware/loggermw/logger.go @@ -64,10 +64,7 @@ func (l *loggerImpl) Middleware() web.Middleware { // put the start time on context so we can measure it later. r = r.WithContext(log.InitstartTime(r.Context(), time.Now())) - //nolint:staticcheck // not yet migrated to OpenFeature - if l.flags.IsEnabled(r.Context(), featuremgmt.FlagUnifiedRequestLog) { - r = r.WithContext(errutil.SetUnifiedLogging(r.Context())) - } + r = r.WithContext(errutil.SetUnifiedLogging(r.Context())) rw := web.Rw(w, r) next.ServeHTTP(rw, r) diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index e9a4a8c7caf..c5aaf9fbf63 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -185,13 +185,6 @@ var ( Stage: FeatureStageExperimental, Owner: grafanaDatasourcesCoreServicesSquad, }, - { - Name: "unifiedRequestLog", - Description: "Writes error logs to the request logger", - Stage: FeatureStageGeneralAvailability, - Owner: grafanaBackendGroup, - Expression: "true", - }, { Name: "renderAuthJWT", Description: "Uses JWT-based auth for rendering instead of relying on remote cache", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index d56aff17bb7..6889cb9c040 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -24,7 +24,6 @@ influxqlStreamingParser,experimental,@grafana/partner-datasources,false,false,fa influxdbRunQueriesInParallel,privatePreview,@grafana/partner-datasources,false,false,false lokiLogsDataplane,experimental,@grafana/observability-logs,false,false,false disableSSEDataplane,experimental,@grafana/grafana-datasources-core-services,false,false,false -unifiedRequestLog,GA,@grafana/grafana-backend-group,false,false,false renderAuthJWT,preview,@grafana/grafana-operator-experience-squad,false,false,false refactorVariablesTimeRange,preview,@grafana/dashboards-squad,false,false,false faroDatasourceSelector,preview,@grafana/app-o11y,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index fd77326e8b0..2797b046d57 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -79,10 +79,6 @@ const ( // Disables dataplane specific processing in server side expressions. FlagDisableSSEDataplane = "disableSSEDataplane" - // FlagUnifiedRequestLog - // Writes error logs to the request logger - FlagUnifiedRequestLog = "unifiedRequestLog" - // FlagRenderAuthJWT // Uses JWT-based auth for rendering instead of relying on remote cache FlagRenderAuthJWT = "renderAuthJWT" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index a0afcd87626..63257b6d738 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -3515,7 +3515,8 @@ "metadata": { "name": "unifiedRequestLog", "resourceVersion": "1764664939750", - "creationTimestamp": "2023-03-31T13:38:09Z" + "creationTimestamp": "2023-03-31T13:38:09Z", + "deletionTimestamp": "2025-12-18T14:21:02Z" }, "spec": { "description": "Writes error logs to the request logger", From 285f2b1d3257d8ce3a74a0cc4c40b4fd4ab0958e Mon Sep 17 00:00:00 2001 From: Misi Date: Fri, 19 Dec 2025 10:28:20 +0100 Subject: [PATCH 246/297] Auth: Allow service accounts to authenticate to ST Grafana (#115536) * Allow SAs to authn ext_jwt * Address feedback --- pkg/services/authn/clients/ext_jwt.go | 3 +- pkg/services/authn/clients/ext_jwt_test.go | 36 +++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/pkg/services/authn/clients/ext_jwt.go b/pkg/services/authn/clients/ext_jwt.go index b479f314c76..be9efa9ce76 100644 --- a/pkg/services/authn/clients/ext_jwt.go +++ b/pkg/services/authn/clients/ext_jwt.go @@ -131,7 +131,8 @@ func (s *ExtendedJWT) authenticateAsUser( return nil, errExtJWTInvalid.Errorf("failed to parse id token subject: %w", err) } - if !claims.IsIdentityType(t, claims.TypeUser) { + // TODO: How to support other identity types like render and anonymous here? + if !claims.IsIdentityType(t, claims.TypeUser, claims.TypeServiceAccount) { return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", idTokenClaims.Subject) } diff --git a/pkg/services/authn/clients/ext_jwt_test.go b/pkg/services/authn/clients/ext_jwt_test.go index 88cf11fb83a..3c7203844b1 100644 --- a/pkg/services/authn/clients/ext_jwt_test.go +++ b/pkg/services/authn/clients/ext_jwt_test.go @@ -53,6 +53,17 @@ var ( Namespace: "default", // org ID of 1 is special and translates to default }, } + validIDTokenClaimsWithServiceAccount = idTokenClaims{ + Claims: jwt.Claims{ + Subject: "service-account:3", + Expiry: jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)), + IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)), + }, + Rest: authnlib.IDTokenClaims{ + AuthenticatedBy: "extended_jwt", + Namespace: "default", // org ID of 1 is special and translates to default + }, + } validIDTokenClaimsWithStackSet = idTokenClaims{ Claims: jwt.Claims{ Subject: "user:2", @@ -118,7 +129,7 @@ var ( } invalidSubjectIDTokenClaims = idTokenClaims{ Claims: jwt.Claims{ - Subject: "service-account:2", + Subject: "anonymous:2", Expiry: jwt.NewNumericDate(time.Date(2023, 5, 3, 0, 0, 0, 0, time.UTC)), IssuedAt: jwt.NewNumericDate(time.Date(2023, 5, 2, 0, 0, 0, 0, time.UTC)), }, @@ -286,6 +297,29 @@ func TestExtendedJWT_Authenticate(t *testing.T) { }, }, }, + { + name: "should authenticate as service account", + accessToken: &validAccessTokenClaims, + idToken: &validIDTokenClaimsWithServiceAccount, + orgID: 1, + want: &authn.Identity{ + ID: "3", + Type: claims.TypeServiceAccount, + OrgID: 1, + AccessTokenClaims: &validAccessTokenClaims, + IDTokenClaims: &validIDTokenClaimsWithServiceAccount, + Namespace: "default", + AuthenticatedBy: "extendedjwt", + AuthID: "access-policy:this-uid", + ClientParams: authn.ClientParams{ + FetchSyncedUser: true, + SyncPermissions: true, + FetchPermissionsParams: authn.FetchPermissionsParams{ + RestrictedActions: []string{"dashboards:create", "folders:read", "datasources:explore", "datasources.insights:read"}, + }, + }, + }, + }, { name: "should authenticate as user in the user namespace", accessToken: &validAccessTokenClaimsWildcard, From b5793a5f73a9be355f3a39947867783c0d8d64a2 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Fri, 19 Dec 2025 11:43:46 +0100 Subject: [PATCH 247/297] Alerting: Fix receiver_name and has_prometheus_definition filters with compact=true (#115582) --- .../ngalert/api/api_prometheus_test.go | 51 ++++++++++++ pkg/services/ngalert/store/alert_rule.go | 24 ++++-- pkg/services/ngalert/store/compat.go | 25 +++--- pkg/services/ngalert/store/compat_test.go | 82 ++++++++++++++++++- 4 files changed, 163 insertions(+), 19 deletions(-) diff --git a/pkg/services/ngalert/api/api_prometheus_test.go b/pkg/services/ngalert/api/api_prometheus_test.go index 71e25d4c963..75ec6c901fd 100644 --- a/pkg/services/ngalert/api/api_prometheus_test.go +++ b/pkg/services/ngalert/api/api_prometheus_test.go @@ -2169,6 +2169,57 @@ func TestRouteGetRuleStatuses(t *testing.T) { }) }) + t.Run("compact mode with receiver_name filter returns only matching rules", func(t *testing.T) { + fakeStore, _, api := setupAPI(t) + + ruleA := gen.With( + gen.WithGroupKey(ngmodels.AlertRuleGroupKey{ + NamespaceUID: "folder-1", + RuleGroup: "group-1", + OrgID: orgID, + }), + gen.WithNotificationSettings( + ngmodels.NotificationSettings{ + Receiver: "receiver-a", + GroupBy: []string{"alertname"}, + }, + ), + ).GenerateRef() + fakeStore.PutRule(context.Background(), ruleA) + + ruleB := gen.With( + gen.WithGroupKey(ngmodels.AlertRuleGroupKey{ + NamespaceUID: "folder-2", + RuleGroup: "group-2", + OrgID: orgID, + }), + gen.WithNotificationSettings( + ngmodels.NotificationSettings{ + Receiver: "receiver-b", + GroupBy: []string{"alertname"}, + }, + ), + ).GenerateRef() + fakeStore.PutRule(context.Background(), ruleB) + r, err := http.NewRequest("GET", "/api/v1/rules?compact=true&receiver_name=receiver-a", nil) + require.NoError(t, err) + c := &contextmodel.ReqContext{ + Context: &web.Context{Req: r}, + SignedInUser: &user.SignedInUser{ + OrgID: orgID, + Permissions: queryPermissions, + }, + } + resp := api.RouteGetRuleStatuses(c) + require.Equal(t, http.StatusOK, resp.Status()) + var res apimodels.RuleResponse + require.NoError(t, json.Unmarshal(resp.Body(), &res)) + + require.Len(t, res.Data.RuleGroups, 1) + require.Equal(t, "group-1", res.Data.RuleGroups[0].Name) + require.Empty(t, res.Data.RuleGroups[0].Rules[0].Query, "Query should be empty in compact mode") + }) + t.Run("provenance as expected", func(t *testing.T) { fakeStore, fakeAIM, api, provStore := setupAPIFull(t) // Rule without provenance diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index 5957e7026b2..30856587794 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -642,6 +642,23 @@ func (st DBstore) ListAlertRulesByGroup(ctx context.Context, query *ngmodels.Lis _ = rows.Close() }() + opts := AlertRuleConvertOptions{} + if query.Compact { + opts.ExcludeAlertQueries = true + opts.ExcludeNotificationSettings = true + opts.ExcludeMetadata = true + + if query.ReceiverName != "" || query.TimeIntervalName != "" { + // Need NotificationSettings for these filters + opts.ExcludeNotificationSettings = false + } + + if query.HasPrometheusRuleDefinition != nil { + // Need Metadata for this filter + opts.ExcludeMetadata = false + } + } + // Process rules and implement per-group pagination var groupsFetched int64 var rulesFetched int64 @@ -653,12 +670,7 @@ func (st DBstore) ListAlertRulesByGroup(ctx context.Context, query *ngmodels.Lis continue } - var converted ngmodels.AlertRule - if query.Compact { - converted, err = alertRuleToModelsAlertRuleCompact(*rule, st.Logger) - } else { - converted, err = alertRuleToModelsAlertRule(*rule, st.Logger) - } + converted, err := convertAlertRuleToModel(*rule, st.Logger, opts) if err != nil { st.Logger.Error("Invalid rule found in DB store, cannot convert, ignoring it", "func", "ListAlertRulesByGroup", "error", err) diff --git a/pkg/services/ngalert/store/compat.go b/pkg/services/ngalert/store/compat.go index fbb69addc72..57dcf411853 100644 --- a/pkg/services/ngalert/store/compat.go +++ b/pkg/services/ngalert/store/compat.go @@ -15,22 +15,23 @@ type compactQuery struct { DatasourceUID string `json:"datasourceUid"` } -func alertRuleToModelsAlertRule(ar alertRule, l log.Logger) (models.AlertRule, error) { - return convertAlertRuleToModel(ar, l, false) +// AlertRuleConvertOptions controls which fields to parse during conversion from alertRule to models.AlertRule. +// By default all fields are included. Set Exclude* to true to skip parsing expensive fields. +type AlertRuleConvertOptions struct { + ExcludeAlertQueries bool // Only parse datasource UIDs from queries + ExcludeNotificationSettings bool + ExcludeMetadata bool } -// alertRuleToModelsAlertRuleCompact transforms an alertRule to a models.AlertRule -// ignoring alert queries (except for data source UIDs), notification settings, and metadata. -func alertRuleToModelsAlertRuleCompact(ar alertRule, l log.Logger) (models.AlertRule, error) { - return convertAlertRuleToModel(ar, l, true) +func alertRuleToModelsAlertRule(ar alertRule, l log.Logger) (models.AlertRule, error) { + return convertAlertRuleToModel(ar, l, AlertRuleConvertOptions{}) } // convertAlertRuleToModel creates a models.AlertRule from an alertRule. -// When 'compact' is set to 'true', it skips parsing the alert queries (except for the data source UID), notification -// settings, and metadata, thus reducing the number of JSON serializations needed. -func convertAlertRuleToModel(ar alertRule, l log.Logger, compact bool) (models.AlertRule, error) { +// opts.Exclude* fields control which expensive fields to skip parsing, reducing JSON serializations. +func convertAlertRuleToModel(ar alertRule, l log.Logger, opts AlertRuleConvertOptions) (models.AlertRule, error) { var data []models.AlertQuery - if compact { + if opts.ExcludeAlertQueries { var cqs []compactQuery if err := json.Unmarshal([]byte(ar.Data), &cqs); err != nil { return models.AlertRule{}, fmt.Errorf("failed to parse data: %w", err) @@ -118,7 +119,7 @@ func convertAlertRuleToModel(ar alertRule, l log.Logger, compact bool) (models.A } } - if !compact && ar.NotificationSettings != "" { + if !opts.ExcludeNotificationSettings && ar.NotificationSettings != "" { ns, err := parseNotificationSettings(ar.NotificationSettings) if err != nil { return models.AlertRule{}, fmt.Errorf("failed to parse notification settings: %w", err) @@ -126,7 +127,7 @@ func convertAlertRuleToModel(ar alertRule, l log.Logger, compact bool) (models.A result.NotificationSettings = ns } - if !compact && ar.Metadata != "" { + if !opts.ExcludeMetadata && ar.Metadata != "" { err = json.Unmarshal([]byte(ar.Metadata), &result.Metadata) if err != nil { return models.AlertRule{}, fmt.Errorf("failed to metadata: %w", err) diff --git a/pkg/services/ngalert/store/compat_test.go b/pkg/services/ngalert/store/compat_test.go index ef80f51d668..d4aab40931f 100644 --- a/pkg/services/ngalert/store/compat_test.go +++ b/pkg/services/ngalert/store/compat_test.go @@ -84,7 +84,11 @@ func TestAlertRuleToModelsAlertRuleCompact(t *testing.T) { Metadata: `{"editor_settings":{"simplified_query_and_expressions_section":true}}`, } - compactResult, err := alertRuleToModelsAlertRuleCompact(rule, &logtest.Fake{}) + compactResult, err := convertAlertRuleToModel(rule, &logtest.Fake{}, AlertRuleConvertOptions{ + ExcludeAlertQueries: true, + ExcludeNotificationSettings: true, + ExcludeMetadata: true, + }) require.NoError(t, err) // Should have datasource UIDs. @@ -142,6 +146,82 @@ func TestAlertRuleToModelsAlertRuleCompact(t *testing.T) { // Should have metadata (metadata is parsed from JSON to struct). require.NotEqual(t, ngmodels.AlertRuleMetadata{}, fullResult.Metadata) }) + + t.Run("compact mode with notification settings included for filtering", func(t *testing.T) { + rule := alertRule{ + ID: 1, + OrgID: 1, + UID: "test-uid", + Title: "Test Rule", + Condition: "A", + Data: `[{"datasourceUid":"ds1","refId":"A","queryType":"test","model":{"expr":"up"}}]`, + IntervalSeconds: 60, + Version: 1, + NamespaceUID: "ns-uid", + RuleGroup: "test-group", + NoDataState: "NoData", + ExecErrState: "Error", + NotificationSettings: `[{"receiver":"test-receiver"}]`, + Metadata: `{"editor_settings":{"simplified_query_and_expressions_section":true}}`, + } + + result, err := convertAlertRuleToModel(rule, &logtest.Fake{}, AlertRuleConvertOptions{ + ExcludeAlertQueries: true, + ExcludeNotificationSettings: false, + ExcludeMetadata: true, + }) + require.NoError(t, err) + + // Should have compact query data (only datasource UIDs). + require.Len(t, result.Data, 1) + require.Equal(t, "ds1", result.Data[0].DatasourceUID) + require.Empty(t, result.Data[0].RefID) + + // Should have notification settings for filtering. + require.Len(t, result.NotificationSettings, 1) + require.Equal(t, "test-receiver", result.NotificationSettings[0].Receiver) + + // Should not have metadata. + require.Equal(t, ngmodels.AlertRuleMetadata{}, result.Metadata) + }) + + t.Run("compact mode with metadata included for filtering", func(t *testing.T) { + rule := alertRule{ + ID: 1, + OrgID: 1, + UID: "test-uid", + Title: "Test Rule", + Condition: "A", + Data: `[{"datasourceUid":"ds1","refId":"A","queryType":"test","model":{"expr":"up"}}]`, + IntervalSeconds: 60, + Version: 1, + NamespaceUID: "ns-uid", + RuleGroup: "test-group", + NoDataState: "NoData", + ExecErrState: "Error", + NotificationSettings: `[{"receiver":"test-receiver"}]`, + Metadata: `{"prometheus_style_rule":{"original_rule_definition":"alert: TestAlert\n expr: rate(metric[5m]) > 1"}}`, + } + + result, err := convertAlertRuleToModel(rule, &logtest.Fake{}, AlertRuleConvertOptions{ + ExcludeAlertQueries: true, + ExcludeNotificationSettings: true, + ExcludeMetadata: false, + }) + require.NoError(t, err) + + // Should have compact query data (only datasource UIDs). + require.Len(t, result.Data, 1) + require.Equal(t, "ds1", result.Data[0].DatasourceUID) + require.Empty(t, result.Data[0].RefID) + + // Should not have notification settings. + require.Empty(t, result.NotificationSettings) + + // Should have metadata for filtering. + require.NotEqual(t, ngmodels.AlertRuleMetadata{}, result.Metadata) + require.True(t, result.HasPrometheusRuleDefinition()) + }) } func TestAlertRuleVersionToAlertRule(t *testing.T) { From a0751b6e71d5d6a1165fb7222d0858d147f3e423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Jim=C3=A9nez=20S=C3=A1nchez?= Date: Fri, 19 Dec 2025 12:44:15 +0100 Subject: [PATCH 248/297] Provisioning: Default to folder sync only and block new instance sync repositories (#115569) * Default to folder sync only and block new instance sync repositories - Change default allowed_targets to folder-only in backend configuration - Modify validation to only enforce allowedTargets on CREATE operations - Add deprecation warning for existing instance sync repositories - Update frontend defaults and tests to reflect new behavior Fixes #619 * Update warning message: change 'deprecated' to 'not fully supported' * Fix health check: don't validate allowedTargets for existing repositories Health checks for existing repositories should treat them as UPDATE operations, not CREATE operations, so they don't fail validation for instance sync target. * Fix tests and update i18n translations - Update BootstrapStep tests to reflect folder-only default behavior - Run i18n-extract to update translation file structure * Fix integration tests * Fix tests * Fix provisioning test wizard * Fix fronted test --- apps/provisioning/pkg/repository/tester.go | 8 +- apps/provisioning/pkg/repository/validator.go | 6 +- .../pkg/repository/validator_test.go | 3 +- conf/defaults.ini | 2 +- pkg/registry/apis/provisioning/register.go | 3 +- pkg/setting/setting.go | 2 +- pkg/tests/apis/provisioning/exportjob_test.go | 2 + pkg/tests/apis/provisioning/helper_test.go | 3 + .../apis/provisioning/job_validation_test.go | 4 +- pkg/tests/apis/provisioning/movejob_test.go | 6 +- .../apis/provisioning/repository_test.go | 12 ++- pkg/tests/apis/provisioning/stats_test.go | 7 +- pkg/tests/testinfra/testinfra.go | 7 ++ .../provisioning/Config/ConfigForm.tsx | 5 +- .../features/provisioning/Config/defaults.ts | 2 +- .../provisioning/Shared/RepositoryList.tsx | 12 +++ .../Wizard/BootstrapStep.test.tsx | 73 +++++++++++++------ .../Wizard/ProvisioningWizard.test.tsx | 15 +++- .../Wizard/hooks/useModeOptions.ts | 2 +- public/locales/en-US/grafana.json | 4 + 20 files changed, 128 insertions(+), 50 deletions(-) diff --git a/apps/provisioning/pkg/repository/tester.go b/apps/provisioning/pkg/repository/tester.go index 6bd65443d92..14290d0514b 100644 --- a/apps/provisioning/pkg/repository/tester.go +++ b/apps/provisioning/pkg/repository/tester.go @@ -23,7 +23,13 @@ func NewSimpleRepositoryTester(validator RepositoryValidator) SimpleRepositoryTe // TestRepository validates the repository and then runs a health check func (t *SimpleRepositoryTester) TestRepository(ctx context.Context, repo Repository) (*provisioning.TestResults, error) { - errors := t.validator.ValidateRepository(repo) + // Determine if this is a CREATE or UPDATE operation + // If the repository has been observed by the controller (ObservedGeneration > 0), + // it's an existing repository and we should treat it as UPDATE + cfg := repo.Config() + isCreate := cfg.Status.ObservedGeneration == 0 + + errors := t.validator.ValidateRepository(repo, isCreate) if len(errors) > 0 { rsp := &provisioning.TestResults{ Code: http.StatusUnprocessableEntity, // Invalid diff --git a/apps/provisioning/pkg/repository/validator.go b/apps/provisioning/pkg/repository/validator.go index 73198f0d7ee..46ba81f8cbd 100644 --- a/apps/provisioning/pkg/repository/validator.go +++ b/apps/provisioning/pkg/repository/validator.go @@ -32,7 +32,9 @@ func NewValidator(minSyncInterval time.Duration, allowedTargets []provisioning.S } // ValidateRepository solely does configuration checks on the repository object. It does not run a health check or compare against existing repositories. -func (v *RepositoryValidator) ValidateRepository(repo Repository) field.ErrorList { +// isCreate indicates whether this is a CREATE operation (true) or UPDATE operation (false). +// When isCreate is false, allowedTargets validation is skipped to allow existing repositories to continue working. +func (v *RepositoryValidator) ValidateRepository(repo Repository, isCreate bool) field.ErrorList { list := repo.Validate() cfg := repo.Config() @@ -44,7 +46,7 @@ func (v *RepositoryValidator) ValidateRepository(repo Repository) field.ErrorLis if cfg.Spec.Sync.Target == "" { list = append(list, field.Required(field.NewPath("spec", "sync", "target"), "The target type is required when sync is enabled")) - } else if !slices.Contains(v.allowedTargets, cfg.Spec.Sync.Target) { + } else if isCreate && !slices.Contains(v.allowedTargets, cfg.Spec.Sync.Target) { list = append(list, field.Invalid( field.NewPath("spec", "target"), diff --git a/apps/provisioning/pkg/repository/validator_test.go b/apps/provisioning/pkg/repository/validator_test.go index cb8b726a9bf..75980733e17 100644 --- a/apps/provisioning/pkg/repository/validator_test.go +++ b/apps/provisioning/pkg/repository/validator_test.go @@ -303,7 +303,8 @@ func TestValidateRepository(t *testing.T) { validator := NewValidator(10*time.Second, []provisioning.SyncTargetType{provisioning.SyncTargetTypeFolder}, false) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - errors := validator.ValidateRepository(tt.repository) + // Tests validate new configurations, so always pass isCreate=true + errors := validator.ValidateRepository(tt.repository, true) require.Len(t, errors, tt.expectedErrs) if tt.validateError != nil { tt.validateError(t, errors) diff --git a/conf/defaults.ini b/conf/defaults.ini index 8ee03e6a34c..28cd0420eb2 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -2264,7 +2264,7 @@ fail_tests_on_console = true # List of targets that can be controlled by a repository, separated by |. # Instance means the whole grafana instance will be controlled by a repository. # Folder limits it to a folder within the grafana instance. -allowed_targets = instance|folder +allowed_targets = folder # Whether image rendering is allowed for dashboard previews. # Requires image rendering service to be configured. diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index 80c22338ae0..f5376cb20ab 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -673,7 +673,8 @@ func (b *APIBuilder) Validate(ctx context.Context, a admission.Attributes, o adm // // the only time to add configuration checks here is if you need to compare // the incoming change to the current configuration - list := b.validator.ValidateRepository(repo) + isCreate := a.GetOperation() == admission.Create + list := b.validator.ValidateRepository(repo, isCreate) cfg := repo.Config() if a.GetOperation() == admission.Update { diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 00bcdabd88d..995a01ad825 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -2167,7 +2167,7 @@ func (cfg *Cfg) readProvisioningSettings(iniFile *ini.File) error { } cfg.ProvisioningAllowedTargets = iniFile.Section("provisioning").Key("allowed_targets").Strings("|") if len(cfg.ProvisioningAllowedTargets) == 0 { - cfg.ProvisioningAllowedTargets = []string{"instance", "folder"} + cfg.ProvisioningAllowedTargets = []string{"folder"} } cfg.ProvisioningAllowImageRendering = iniFile.Section("provisioning").Key("allow_image_rendering").MustBool(true) cfg.ProvisioningMinSyncInterval = iniFile.Section("provisioning").Key("min_sync_interval").MustDuration(10 * time.Second) diff --git a/pkg/tests/apis/provisioning/exportjob_test.go b/pkg/tests/apis/provisioning/exportjob_test.go index 2218e87ef77..5d4348d23cb 100644 --- a/pkg/tests/apis/provisioning/exportjob_test.go +++ b/pkg/tests/apis/provisioning/exportjob_test.go @@ -44,6 +44,7 @@ func TestIntegrationProvisioning_ExportUnifiedToRepository(t *testing.T) { const repo = "local-repository" testRepo := TestRepo{ Name: repo, + Target: "instance", // Export is only supported for instance sync Copies: map[string]string{}, // No initial files needed for export test ExpectedDashboards: 4, // 4 dashboards created above (v0, v1, v2alpha1, v2beta1) ExpectedFolders: 0, // No folders expected after sync @@ -177,6 +178,7 @@ func TestIntegrationProvisioning_ExportDashboardsWithStoredVersions(t *testing.T const repo = "version-test-repository" testRepo := TestRepo{ Name: repo, + Target: "instance", // Export is only supported for instance sync Copies: map[string]string{}, ExpectedDashboards: len(tests), ExpectedFolders: 0, diff --git a/pkg/tests/apis/provisioning/helper_test.go b/pkg/tests/apis/provisioning/helper_test.go index 0b6a5e3c806..791ac4b8a20 100644 --- a/pkg/tests/apis/provisioning/helper_test.go +++ b/pkg/tests/apis/provisioning/helper_test.go @@ -695,6 +695,9 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper }, }, PermittedProvisioningPaths: ".|" + provisioningPath, + // Allow both folder and instance sync targets for tests + // (instance is needed for export jobs, folder for most operations) + ProvisioningAllowedTargets: []string{"folder", "instance"}, } for _, o := range options { o(&opts) diff --git a/pkg/tests/apis/provisioning/job_validation_test.go b/pkg/tests/apis/provisioning/job_validation_test.go index c54ad58500c..4c1947693f1 100644 --- a/pkg/tests/apis/provisioning/job_validation_test.go +++ b/pkg/tests/apis/provisioning/job_validation_test.go @@ -24,10 +24,10 @@ func TestIntegrationProvisioning_JobValidation(t *testing.T) { const repo = "job-validation-test-repo" testRepo := TestRepo{ Name: repo, - Target: "instance", + Target: "folder", Copies: map[string]string{}, ExpectedDashboards: 0, - ExpectedFolders: 0, + ExpectedFolders: 1, // folder sync creates a folder } helper.CreateRepo(t, testRepo) diff --git a/pkg/tests/apis/provisioning/movejob_test.go b/pkg/tests/apis/provisioning/movejob_test.go index 85d227996fd..9cd298711a9 100644 --- a/pkg/tests/apis/provisioning/movejob_test.go +++ b/pkg/tests/apis/provisioning/movejob_test.go @@ -24,14 +24,15 @@ func TestIntegrationProvisioning_MoveJob(t *testing.T) { ctx := context.Background() const repo = "move-test-repo" testRepo := TestRepo{ - Name: repo, + Name: repo, + Target: "folder", Copies: map[string]string{ "testdata/all-panels.json": "dashboard1.json", "testdata/text-options.json": "dashboard2.json", "testdata/timeline-demo.json": "folder/dashboard3.json", }, ExpectedDashboards: 3, - ExpectedFolders: 1, + ExpectedFolders: 2, // folder sync creates a folder for the repo + one nested folder } helper.CreateRepo(t, testRepo) @@ -236,6 +237,7 @@ func TestIntegrationProvisioning_MoveJob(t *testing.T) { const refRepo = "move-ref-test-repo" helper.CreateRepo(t, TestRepo{ Name: refRepo, + Target: "folder", SkipResourceAssertions: true, // HACK: I am not sure why sometimes it's 6 or 3 dashbaords. }) diff --git a/pkg/tests/apis/provisioning/repository_test.go b/pkg/tests/apis/provisioning/repository_test.go index 877ed0d5f98..3cee3cf4aba 100644 --- a/pkg/tests/apis/provisioning/repository_test.go +++ b/pkg/tests/apis/provisioning/repository_test.go @@ -578,7 +578,13 @@ func TestIntegrationProvisioning_RunLocalRepository(t *testing.T) { const targetPath = "all-panels.json" // Set up the repository. - helper.CreateRepo(t, TestRepo{Name: repo}) + helper.CreateRepo(t, TestRepo{ + Name: repo, + Target: "folder", + ExpectedDashboards: 0, + ExpectedFolders: 1, // folder sync creates a folder for the repo + SkipResourceAssertions: false, + }) // Write a file -- this will create it *both* in the local file system, and in grafana t.Run("write all panels", func(t *testing.T) { @@ -744,10 +750,10 @@ func TestIntegrationProvisioning_ImportAllPanelsFromLocalRepository(t *testing.T // Set up the repository and the file to import. testRepo := TestRepo{ Name: repo, - Target: "instance", + Target: "folder", Copies: map[string]string{"testdata/all-panels.json": "all-panels.json"}, ExpectedDashboards: 1, - ExpectedFolders: 0, + ExpectedFolders: 1, // folder sync creates a folder } // We create the repository helper.CreateRepo(t, testRepo) diff --git a/pkg/tests/apis/provisioning/stats_test.go b/pkg/tests/apis/provisioning/stats_test.go index eae3cbf7531..25ecd5c8f7c 100644 --- a/pkg/tests/apis/provisioning/stats_test.go +++ b/pkg/tests/apis/provisioning/stats_test.go @@ -21,13 +21,14 @@ func TestIntegrationProvisioning_Stats(t *testing.T) { const repo = "stats-test-repo1" testRepo := TestRepo{ - Name: repo, + Name: repo, + Target: "folder", Copies: map[string]string{ "testdata/all-panels.json": "dashboard1.json", "testdata/text-options.json": "folder/dashboard2.json", }, ExpectedDashboards: 2, - ExpectedFolders: 1, + ExpectedFolders: 2, // folder sync creates a folder for the repo + one nested folder } helper.CreateRepo(t, testRepo) @@ -94,7 +95,7 @@ func TestIntegrationProvisioning_Stats(t *testing.T) { require.Equal(t, int64(2), count, "repo should manage 2 dashboards") } else if group == "folder.grafana.app" && resource == "folders" { count, _, _ := unstructured.NestedInt64(stat, "count") - require.Equal(t, int64(1), count, "repo should manage 1 folder") + require.Equal(t, int64(2), count, "repo should manage 2 folders (repo folder + nested folder)") } } } diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index c84ddd6fb40..6e2f0660c32 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -580,6 +580,12 @@ func CreateGrafDir(t *testing.T, opts GrafanaOpts) (string, string) { _, err = pathsSect.NewKey("permitted_provisioning_paths", opts.PermittedProvisioningPaths) require.NoError(t, err) } + if len(opts.ProvisioningAllowedTargets) > 0 { + provisioningSect, err := getOrCreateSection("provisioning") + require.NoError(t, err) + _, err = provisioningSect.NewKey("allowed_targets", strings.Join(opts.ProvisioningAllowedTargets, "|")) + require.NoError(t, err) + } if opts.EnableSCIM { scimSection, err := getOrCreateSection("auth.scim") require.NoError(t, err) @@ -669,6 +675,7 @@ type GrafanaOpts struct { UnifiedStorageEnableSearch bool UnifiedStorageMaxPageSizeBytes int PermittedProvisioningPaths string + ProvisioningAllowedTargets []string GrafanaComSSOAPIToken string LicensePath string EnableRecordingRules bool diff --git a/public/app/features/provisioning/Config/ConfigForm.tsx b/public/app/features/provisioning/Config/ConfigForm.tsx index a29bc68329e..9c86403fee6 100644 --- a/public/app/features/provisioning/Config/ConfigForm.tsx +++ b/public/app/features/provisioning/Config/ConfigForm.tsx @@ -80,10 +80,7 @@ export function ConfigForm({ data }: ConfigFormProps) { const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); const [type, readOnly] = watch(['type', 'readOnly']); - const targetOptions = useMemo( - () => getTargetOptions(settings.data?.allowedTargets || ['instance', 'folder']), - [settings.data] - ); + const targetOptions = useMemo(() => getTargetOptions(settings.data?.allowedTargets || ['folder']), [settings.data]); const isGitBased = isGitProvider(type); const { diff --git a/public/app/features/provisioning/Config/defaults.ts b/public/app/features/provisioning/Config/defaults.ts index 7a8f27ae456..19d17fb4da7 100644 --- a/public/app/features/provisioning/Config/defaults.ts +++ b/public/app/features/provisioning/Config/defaults.ts @@ -11,7 +11,7 @@ export interface GetDefaultValuesOptions { export function getDefaultValues({ repository, - allowedTargets = ['instance', 'folder'], + allowedTargets = ['folder'], }: GetDefaultValuesOptions = {}): RepositoryFormData { if (!repository) { const defaultTarget = allowedTargets.includes('folder') ? 'folder' : 'instance'; diff --git a/public/app/features/provisioning/Shared/RepositoryList.tsx b/public/app/features/provisioning/Shared/RepositoryList.tsx index 038359c6e8b..76dab485a74 100644 --- a/public/app/features/provisioning/Shared/RepositoryList.tsx +++ b/public/app/features/provisioning/Shared/RepositoryList.tsx @@ -22,6 +22,7 @@ export function RepositoryList({ items }: Props) { const filteredItems = items.filter((item) => item.metadata?.name?.includes(query)); const { instanceConnected } = checkSyncSettings(items); + const hasInstanceSyncRepo = items.some((item) => item.spec?.sync?.target === 'instance'); const getResourceCountSection = () => { if (isProvisionedInstance) { @@ -77,6 +78,17 @@ export function RepositoryList({ items }: Props) { return ( <> {getResourceCountSection()} + {hasInstanceSyncRepo && ( + + + Instance sync is currently not fully supported and breaks library panels and alerts. To use library panels + and alerts, disconnect your repository and reconnect it using folder sync instead. + + + )} {!instanceConnected && ( diff --git a/public/app/features/provisioning/Wizard/BootstrapStep.test.tsx b/public/app/features/provisioning/Wizard/BootstrapStep.test.tsx index 90d566415b9..24d7e322ac3 100644 --- a/public/app/features/provisioning/Wizard/BootstrapStep.test.tsx +++ b/public/app/features/provisioning/Wizard/BootstrapStep.test.tsx @@ -32,7 +32,7 @@ function FormWrapper({ children, defaultValues }: { children: ReactNode; default url: 'https://github.com/test/repo', title: '', sync: { - target: 'instance', + target: 'folder', enabled: true, }, branch: 'main', @@ -101,12 +101,6 @@ describe('BootstrapStep', () => { (useModeOptions as jest.Mock).mockReturnValue({ enabledOptions: [ - { - target: 'instance', - label: 'Sync all resources with external storage', - description: 'Resources will be synced with external storage', - subtitle: 'Use this option if you want to sync your entire instance', - }, { target: 'folder', label: 'Sync external storage to a new Grafana folder', @@ -142,8 +136,8 @@ describe('BootstrapStep', () => { it('should render correct info for GitHub repository type', async () => { setup(); - expect(screen.getAllByText('External storage')).toHaveLength(2); - expect(screen.getAllByText('Empty')).toHaveLength(4); // Four elements should show "Empty" (2 external + 2 unmanaged, one per card) + expect(screen.getAllByText('External storage')).toHaveLength(1); // Only folder sync is shown by default + expect(screen.getAllByText('Empty')).toHaveLength(2); // Two elements should have the role "Empty" (1 external + 1 unmanaged) }); it('should render correct info for local file repository type', async () => { @@ -171,10 +165,12 @@ describe('BootstrapStep', () => { setup(); - expect(await screen.getAllByText('2 files')).toHaveLength(2); + expect(await screen.getAllByText('2 files')).toHaveLength(1); // Only folder sync is shown by default }); it('should display resource counts when resources exist', async () => { + // Note: Resource counts are only shown for instance sync, but instance sync is not available by default + // This test is kept for when instance sync is explicitly enabled via settings (useGetResourceStatsQuery as jest.Mock).mockReturnValue({ data: { instance: [ @@ -196,10 +192,29 @@ describe('BootstrapStep', () => { shouldSkipSync: false, }); - setup(); + // Mock settings to allow instance sync for this test + (useModeOptions as jest.Mock).mockReturnValue({ + enabledOptions: [ + { + target: 'instance', + label: 'Sync all resources with external storage', + description: 'Resources will be synced with external storage', + subtitle: 'Use this option if you want to sync your entire instance', + }, + ], + disabledOptions: [], + }); - // Two elements display "7 resources": one in the external storage card and one in unmanaged resources card - expect(await screen.findAllByText('7 resources')).toHaveLength(2); + setup({ + settingsData: { + allowedTargets: ['instance', 'folder'], + allowImageRendering: true, + items: [], + availableRepositoryTypes: [], + }, + }); + + expect(await screen.findByText('7 resources')).toBeInTheDocument(); }); }); @@ -208,16 +223,30 @@ describe('BootstrapStep', () => { setup(); const mockUseResourceStats = require('./hooks/useResourceStats').useResourceStats; - expect(mockUseResourceStats).toHaveBeenCalledWith('test-repo', 'instance'); + expect(mockUseResourceStats).toHaveBeenCalledWith('test-repo', 'folder'); + }); + + it('should use useResourceStats hook with settings data', async () => { + setup({ + settingsData: { + allowedTargets: ['folder'], + allowImageRendering: true, + items: [], + availableRepositoryTypes: [], + }, + }); + + const mockUseResourceStats = require('./hooks/useResourceStats').useResourceStats; + expect(mockUseResourceStats).toHaveBeenCalledWith('test-repo', 'folder'); }); }); describe('sync target options', () => { - it('should display both instance and folder options by default', async () => { + it('should display only folder option by default', async () => { setup(); - expect(await screen.findByText('Sync all resources with external storage')).toBeInTheDocument(); expect(await screen.findByText('Sync external storage to a new Grafana folder')).toBeInTheDocument(); + expect(screen.queryByText('Sync all resources with external storage')).not.toBeInTheDocument(); }); it('should only display instance option when legacy storage exists', async () => { @@ -242,6 +271,7 @@ describe('BootstrapStep', () => { setup({ settingsData: { + allowedTargets: ['instance', 'folder'], allowImageRendering: true, items: [], availableRepositoryTypes: [], @@ -264,15 +294,10 @@ describe('BootstrapStep', () => { }); describe('title field visibility', () => { - it('should show title field only for folder sync target', async () => { - const { user } = setup(); - - // Initially should not show title field (default is instance) - expect(screen.queryByRole('textbox', { name: /display name/i })).not.toBeInTheDocument(); - - const folderOption = await screen.findByText('Sync external storage to a new Grafana folder'); - await user.click(folderOption); + it('should show title field for folder sync target', async () => { + setup(); + // Default is folder, so title field should be visible expect(await screen.findByRole('textbox', { name: /display name/i })).toBeInTheDocument(); }); }); diff --git a/public/app/features/provisioning/Wizard/ProvisioningWizard.test.tsx b/public/app/features/provisioning/Wizard/ProvisioningWizard.test.tsx index 24d23acc607..db49c3bb86e 100644 --- a/public/app/features/provisioning/Wizard/ProvisioningWizard.test.tsx +++ b/public/app/features/provisioning/Wizard/ProvisioningWizard.test.tsx @@ -227,6 +227,15 @@ describe('ProvisioningWizard', () => { error: null, refetch: jest.fn(), }); + // Mock files to ensure sync step is not skipped for folder sync + mockUseGetRepositoryFilesQuery.mockReturnValue({ + data: { + items: [{ name: 'test.json', path: 'test.json' }], + }, + isLoading: false, + error: null, + refetch: jest.fn(), + }); const { user } = setup(); await fillConnectionForm(user, 'github', { @@ -500,10 +509,10 @@ describe('ProvisioningWizard', () => { }); it('should show button text changes based on current step', async () => { - // Mock resources to ensure sync step is not skipped - mockUseGetResourceStatsQuery.mockReturnValue({ + // Mock files to ensure sync step is not skipped for folder sync + mockUseGetRepositoryFilesQuery.mockReturnValue({ data: { - instance: [{ group: 'dashboard.grafana.app', count: 1 }], + items: [{ name: 'test.json', path: 'test.json' }], }, isLoading: false, error: null, diff --git a/public/app/features/provisioning/Wizard/hooks/useModeOptions.ts b/public/app/features/provisioning/Wizard/hooks/useModeOptions.ts index 4bf817b091e..3d288ab487d 100644 --- a/public/app/features/provisioning/Wizard/hooks/useModeOptions.ts +++ b/public/app/features/provisioning/Wizard/hooks/useModeOptions.ts @@ -10,7 +10,7 @@ import { ModeOption } from '../types'; */ function filterModeOptions(modeOptions: ModeOption[], repoName: string, settings?: RepositoryViewList): ModeOption[] { const folderConnected = settings?.items?.some((item) => item.target === 'folder' && item.name !== repoName); - const allowedTargets = settings?.allowedTargets || ['instance', 'folder']; + const allowedTargets = settings?.allowedTargets || ['folder']; return modeOptions.map((option) => { if (option.disabled) { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index febd4c0442e..00757c4251d 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -11951,6 +11951,10 @@ "unsupported-repository-type": "Unsupported repository type: {{repositoryType}}" }, "inline-secure-values-warning": "You need to save your access tokens again due to a system update", + "instance-sync-deprecation": { + "message": "Instance sync is currently not fully supported and breaks library panels and alerts. To use library panels and alerts, disconnect your repository and reconnect it using folder sync instead.", + "title": "Instance sync is not fully supported" + }, "job-status": { "label-view-details": "View details", "loading-finished-job": "Loading finished job...", From b4eb02a6f0ec6e8a21f8bd8613cfc9be4fa3dbaa Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Fri, 19 Dec 2025 12:45:15 +0100 Subject: [PATCH 249/297] Plugins: Change pageId parameter type in usePluginDetailsTabs (#115612) * change usePluginDetailsTabs pageId parameter type * add eslint suppressions --- eslint-suppressions.json | 5 ----- .../plugins/admin/components/PluginDetailsPage.tsx | 7 +------ .../features/plugins/admin/hooks/usePluginDetailsTabs.tsx | 8 ++++---- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 1d0d5a2684c..36eb78aaa72 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -2868,11 +2868,6 @@ "count": 1 } }, - "public/app/features/plugins/admin/components/PluginDetailsPage.tsx": { - "@typescript-eslint/consistent-type-assertions": { - "count": 1 - } - }, "public/app/features/plugins/admin/helpers.ts": { "no-restricted-syntax": { "count": 2 diff --git a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx index 0e651a8e4bf..e4252407ce5 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx @@ -17,7 +17,6 @@ import { PluginDetailsSignature } from '../components/PluginDetailsSignature'; import { usePluginDetailsTabs } from '../hooks/usePluginDetailsTabs'; import { usePluginPageExtensions } from '../hooks/usePluginPageExtensions'; import { useGetSingle, useFetchStatus, useFetchDetailsStatus } from '../state/hooks'; -import { PluginTabIds } from '../types'; import { PluginDetailsDeprecatedWarning } from './PluginDetailsDeprecatedWarning'; @@ -50,11 +49,7 @@ export function PluginDetailsPage({ const queryParams = new URLSearchParams(location.search); const plugin = useGetSingle(pluginId); // fetches the plugin settings for this Grafana instance const isNarrowScreen = useMedia('(max-width: 600px)'); - const { navModel, activePageId } = usePluginDetailsTabs( - plugin, - queryParams.get('page') as PluginTabIds, - isNarrowScreen - ); + const { navModel, activePageId } = usePluginDetailsTabs(plugin, queryParams.get('page'), isNarrowScreen); const { actions, info, subtitle } = usePluginPageExtensions(plugin); const { isLoading: isFetchLoading } = useFetchStatus(); const { isLoading: isFetchDetailsLoading } = useFetchDetailsStatus(); diff --git a/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx b/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx index ab616988e3f..6cb8ec39af4 100644 --- a/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx +++ b/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx @@ -18,9 +18,9 @@ type ReturnType = { }; function getCurrentPageId( - pageId: PluginTabIds | undefined, isNarrowScreen: boolean | undefined, - defaultTab: string + defaultTab: string, + pageId?: PluginTabIds | string | null ): PluginTabIds | string { if (!isNarrowScreen && pageId === PluginTabIds.PLUGINDETAILS) { return defaultTab; @@ -30,7 +30,7 @@ function getCurrentPageId( export const usePluginDetailsTabs = ( plugin?: CatalogPlugin, - pageId?: PluginTabIds, + pageId?: PluginTabIds | string | null, isNarrowScreen?: boolean ): ReturnType => { const { loading, error, value: pluginConfig } = usePluginConfig(plugin); @@ -38,7 +38,7 @@ export const usePluginDetailsTabs = ( const defaultTab = useDefaultPage(plugin, pluginConfig); const isPublished = Boolean(plugin?.isPublished); - const currentPageId = getCurrentPageId(pageId, isNarrowScreen, defaultTab); + const currentPageId = getCurrentPageId(isNarrowScreen, defaultTab, pageId); const navModelChildren = useMemo(() => { const canConfigurePlugins = plugin && contextSrv.hasPermissionInMetadata(AccessControlAction.PluginsWrite, plugin); From c2275f6ee4a4b97f21a5e0f0622dd769e4fdf16c Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:03:45 +0100 Subject: [PATCH 250/297] Alerting: Add Cursor frontmatter to CLAUDE.md for auto-loading (#115613) add Cursor frontmatter to CLAUDE.md for auto-loading --- public/app/features/alerting/unified/CLAUDE.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/public/app/features/alerting/unified/CLAUDE.md b/public/app/features/alerting/unified/CLAUDE.md index 124b9da622f..35ea0486876 100644 --- a/public/app/features/alerting/unified/CLAUDE.md +++ b/public/app/features/alerting/unified/CLAUDE.md @@ -1,3 +1,10 @@ +--- +title: Alerting Squad Guidelines +description: Alerting-specific patterns and conventions for Grafana +globs: + - 'public/app/features/alerting/**' +--- + # Alerting Squad - Claude Code Configuration This file provides context for Claude Code when working on the Grafana Alerting codebase. It contains alerting-specific patterns and references to Grafana's coding standards. From e9a2828f668f88d417c9fc00d0c2b581c65aadd8 Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Fri, 19 Dec 2025 13:40:41 +0100 Subject: [PATCH 251/297] Plugins: Add PluginInsights UI (#115616) * Add getInsights endpoint, add new component PluginInsights * fix linting and add styles * add version option to insights request * Add plugininsights tests, remove console.logs * fix the insight items types * Add getting insights to all the mocks to fix the tests * remove deprecated lint package * Add theme colors, added tests to PluginDetailsPanel * Fix eslint error for plugin details page * Add pluginInsights feature toggle * change getInsights with version API call, resolve conflicts with main * fix typecheck and translation * updated UI * update registry go * fix translation * light css changes * remove duplicated feature toggle * fix the build * update plugin insights tests * fix typecheck * rudderstack added, feedback form added * fix translation * Remove isPluginTabId function --- .../src/types/featureToggles.gen.ts | 5 + pkg/services/featuremgmt/registry.go | 8 + pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.json | 14 ++ public/app/features/plugins/admin/api.ts | 16 ++ .../components/PluginDetailsPage.test.tsx | 2 + .../admin/components/PluginDetailsPage.tsx | 4 +- .../components/PluginDetailsPanel.test.tsx | 71 +++++++- .../admin/components/PluginDetailsPanel.tsx | 9 +- .../admin/components/PluginInsights.test.tsx | 171 ++++++++++++++++++ .../admin/components/PluginInsights.tsx | 140 ++++++++++++++ .../plugins/admin/mocks/catalogPlugin.mock.ts | 2 + .../plugins/admin/mocks/mockHelpers.ts | 8 + .../features/plugins/admin/state/actions.ts | 20 +- .../app/features/plugins/admin/state/hooks.ts | 29 ++- .../features/plugins/admin/state/reducer.ts | 5 + public/app/features/plugins/admin/types.ts | 49 +++++ public/locales/en-US/grafana.json | 6 + 18 files changed, 554 insertions(+), 6 deletions(-) create mode 100644 public/app/features/plugins/admin/components/PluginInsights.test.tsx create mode 100644 public/app/features/plugins/admin/components/PluginInsights.tsx diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index ea9f1c22f39..19a8fbf2c44 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -1193,6 +1193,11 @@ export interface FeatureToggles { */ onlyStoreActionSets?: boolean; /** + * Show insights for plugins in the plugin details page + * @default false + */ + pluginInsights?: boolean; + /** * Enables a new panel time settings drawer */ panelTimeSettings?: boolean; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index c5aaf9fbf63..7e876849dfe 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1968,6 +1968,14 @@ var ( Owner: identityAccessTeam, Expression: "true", }, + { + Name: "pluginInsights", + Description: "Show insights for plugins in the plugin details page", + Stage: FeatureStageExperimental, + FrontendOnly: true, + Owner: grafanaPluginsPlatformSquad, + Expression: "false", + }, { Name: "panelTimeSettings", Description: "Enables a new panel time settings drawer", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 6889cb9c040..510c05a815b 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -267,6 +267,7 @@ jaegerEnableGrpcEndpoint,experimental,@grafana/oss-big-tent,false,false,false pluginStoreServiceLoading,experimental,@grafana/plugins-platform-backend,false,false,false newPanelPadding,preview,@grafana/dashboards-squad,false,false,true onlyStoreActionSets,GA,@grafana/identity-access-team,false,false,false +pluginInsights,experimental,@grafana/plugins-platform-backend,false,false,true panelTimeSettings,experimental,@grafana/dashboards-squad,false,false,false elasticsearchRawDSLQuery,experimental,@grafana/partner-datasources,false,false,false kubernetesAnnotations,experimental,@grafana/grafana-backend-services-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 63257b6d738..0db4a887a6a 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2720,6 +2720,20 @@ "expression": "false" } }, + { + "metadata": { + "name": "pluginInsights", + "resourceVersion": "1761300628147", + "creationTimestamp": "2025-10-24T10:10:28Z" + }, + "spec": { + "description": "Show insights for plugins in the plugin details page", + "stage": "experimental", + "codeowner": "@grafana/plugins-platform-backend", + "frontend": true, + "expression": "false" + } + }, { "metadata": { "name": "pluginInstallAPISync", diff --git a/public/app/features/plugins/admin/api.ts b/public/app/features/plugins/admin/api.ts index 74a072ba054..aa5bc32f183 100644 --- a/public/app/features/plugins/admin/api.ts +++ b/public/app/features/plugins/admin/api.ts @@ -8,6 +8,7 @@ import { LocalPlugin, RemotePlugin, CatalogPluginDetails, + CatalogPluginInsights, Version, PluginVersion, InstancePlugin, @@ -47,6 +48,21 @@ export async function getPluginDetails(id: string): Promise { + if (!version) { + throw new Error('Version is required'); + } + try { + const insights = await getBackendSrv().get(`${GCOM_API_ROOT}/plugins/${id}/versions/${version}/insights`); + return insights; + } catch (error) { + if (isFetchError(error)) { + error.isHandled = true; + } + throw error; + } +} + export async function getRemotePlugins(): Promise { try { const { items: remotePlugins }: { items: RemotePlugin[] } = await getBackendSrv().get(`${GCOM_API_ROOT}/plugins`, { diff --git a/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx b/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx index da4eef2f0d4..0ffc93f8f77 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx @@ -62,10 +62,12 @@ const plugin: CatalogPlugin = { angularDetected: false, isFullyInstalled: true, accessControl: {}, + insights: { id: 1, name: 'test-plugin', version: '1.0.0', insights: [] }, }; jest.mock('../state/hooks', () => ({ useGetSingle: jest.fn(), + useGetPluginInsights: jest.fn(), useFetchStatus: jest.fn().mockReturnValue({ isLoading: false }), useFetchDetailsStatus: () => ({ isLoading: false }), useIsRemotePluginsAvailable: () => false, diff --git a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx index e4252407ce5..2a7342e6be8 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx @@ -16,7 +16,7 @@ import { PluginDetailsPanel } from '../components/PluginDetailsPanel'; import { PluginDetailsSignature } from '../components/PluginDetailsSignature'; import { usePluginDetailsTabs } from '../hooks/usePluginDetailsTabs'; import { usePluginPageExtensions } from '../hooks/usePluginPageExtensions'; -import { useGetSingle, useFetchStatus, useFetchDetailsStatus } from '../state/hooks'; +import { useGetSingle, useFetchStatus, useFetchDetailsStatus, useGetPluginInsights } from '../state/hooks'; import { PluginDetailsDeprecatedWarning } from './PluginDetailsDeprecatedWarning'; @@ -48,6 +48,8 @@ export function PluginDetailsPage({ }; const queryParams = new URLSearchParams(location.search); const plugin = useGetSingle(pluginId); // fetches the plugin settings for this Grafana instance + useGetPluginInsights(pluginId, plugin?.isInstalled ? plugin?.installedVersion : plugin?.latestVersion); + const isNarrowScreen = useMedia('(max-width: 600px)'); const { navModel, activePageId } = usePluginDetailsTabs(plugin, queryParams.get('page'), isNarrowScreen); const { actions, info, subtitle } = usePluginPageExtensions(plugin); diff --git a/public/app/features/plugins/admin/components/PluginDetailsPanel.test.tsx b/public/app/features/plugins/admin/components/PluginDetailsPanel.test.tsx index eade37f559c..20787099842 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPanel.test.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPanel.test.tsx @@ -1,11 +1,23 @@ +import userEvent from '@testing-library/user-event'; import { render, screen } from 'test/test-utils'; import { PluginSignatureStatus, PluginSignatureType, PluginType } from '@grafana/data'; +import { config } from '@grafana/runtime'; -import { CatalogPlugin } from '../types'; +import { CatalogPlugin, SCORE_LEVELS } from '../types'; import { PluginDetailsPanel } from './PluginDetailsPanel'; +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + config: { + ...jest.requireActual('@grafana/runtime').config, + featureToggles: { + pluginInsights: false, + }, + }, +})); + const mockPlugin: CatalogPlugin = { description: 'Test plugin description', downloads: 1000, @@ -185,4 +197,61 @@ describe('PluginDetailsPanel', () => { expect(regularLinks).toContainElement(raiseIssueLink); expect(regularLinks).not.toContainElement(websiteLink); }); + + it('should render plugin insights when plugin has insights', async () => { + config.featureToggles.pluginInsights = true; + const pluginWithInsights = { + ...mockPlugin, + insights: { + id: 1, + name: 'test-plugin', + version: '1.0.0', + insights: [ + { + name: 'security', + scoreValue: 90, + scoreLevel: SCORE_LEVELS.EXCELLENT, + items: [ + { + id: 'signature', + name: 'Signature verified', + level: 'ok' as const, + }, + ], + }, + ], + }, + }; + render(); + expect(screen.getByTestId('plugin-insights-container')).toBeInTheDocument(); + expect(screen.getByText('Plugin insights')).toBeInTheDocument(); + expect(screen.queryByText('Security')).toBeInTheDocument(); + await userEvent.click(screen.getByText('Security')); + expect(screen.getByTestId('plugin-insight-item-signature')).toBeInTheDocument(); + }); + + it('should not render plugin insights when plugin has no insights', () => { + const pluginWithoutInsights = { + ...mockPlugin, + insights: undefined, + }; + render(); + expect(screen.queryByTestId('plugin-insights-container')).not.toBeInTheDocument(); + expect(screen.queryByText('Plugin insights')).not.toBeInTheDocument(); + }); + + it('should not render plugin insights when insights array is empty', () => { + const pluginWithEmptyInsights = { + ...mockPlugin, + insights: { + id: 1, + name: 'test-plugin', + version: '1.0.0', + insights: [], + }, + }; + render(); + expect(screen.queryByTestId('plugin-insights-container')).not.toBeInTheDocument(); + expect(screen.queryByText('Plugin insights')).not.toBeInTheDocument(); + }); }); diff --git a/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx b/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx index 00211b61c6e..aa8b6c792ef 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { t, Trans } from '@grafana/i18n'; -import { reportInteraction } from '@grafana/runtime'; +import { config, reportInteraction } from '@grafana/runtime'; import { PageInfoItem } from '@grafana/runtime/internal'; import { Stack, @@ -22,6 +22,8 @@ import { formatDate } from 'app/core/internationalization/dates'; import { CatalogPlugin } from '../types'; +import { PluginInsights } from './PluginInsights'; + type Props = { pluginExtentionsInfo: PageInfoItem[]; plugin: CatalogPlugin; width?: string }; export function PluginDetailsPanel(props: Props): React.ReactElement | null { @@ -69,6 +71,11 @@ export function PluginDetailsPanel(props: Props): React.ReactElement | null { return ( <> + {config.featureToggles.pluginInsights && plugin.insights && plugin.insights?.insights?.length > 0 && ( + + + + )} {pluginExtentionsInfo.map((infoItem, index) => { diff --git a/public/app/features/plugins/admin/components/PluginInsights.test.tsx b/public/app/features/plugins/admin/components/PluginInsights.test.tsx new file mode 100644 index 00000000000..efd064c7172 --- /dev/null +++ b/public/app/features/plugins/admin/components/PluginInsights.test.tsx @@ -0,0 +1,171 @@ +import userEvent from '@testing-library/user-event'; +import { render, screen } from 'test/test-utils'; + +import { CatalogPluginInsights, InsightLevel, SCORE_LEVELS } from '../types'; + +import { PluginInsights } from './PluginInsights'; + +const mockPluginInsights: CatalogPluginInsights = { + id: 1, + name: 'test-plugin', + version: '1.0.0', + insights: [ + { + name: 'security', + scoreValue: 90, + scoreLevel: SCORE_LEVELS.EXCELLENT, + items: [ + { + id: 'signature', + name: 'Signature verified', + description: 'Plugin signature is valid', + level: 'ok' as InsightLevel, + }, + { + id: 'trackingscripts', + name: 'No unsafe JavaScript detected', + level: 'good' as InsightLevel, + }, + ], + }, + { + name: 'quality', + scoreValue: 60, + scoreLevel: SCORE_LEVELS.FAIR, + items: [ + { + id: 'metadatavalid', + name: 'Metadata is valid', + level: 'ok' as InsightLevel, + }, + { + id: 'code-rules', + name: 'Missing code rules', + description: 'Plugin lacks comprehensive code rules', + level: 'warning' as InsightLevel, + }, + ], + }, + ], +}; + +const mockPluginInsightsWithPoorLevel: CatalogPluginInsights = { + id: 3, + name: 'test-plugin-poor', + version: '0.8.0', + insights: [ + { + name: 'quality', + scoreValue: 35, + scoreLevel: SCORE_LEVELS.POOR, + items: [ + { + id: 'legacy-platform', + name: 'Quality issues detected', + level: 'warning' as InsightLevel, + }, + ], + }, + ], +}; + +describe('PluginInsights', () => { + it('should render plugin insights section', () => { + render(); + const insightsSection = screen.getByTestId('plugin-insights-container'); + expect(insightsSection).toBeInTheDocument(); + expect(screen.getByText('Plugin insights')).toBeInTheDocument(); + }); + + it('should render all insight categories with test ids', () => { + render(); + expect(screen.getByTestId('plugin-insight-security')).toBeInTheDocument(); + expect(screen.getByTestId('plugin-insight-quality')).toBeInTheDocument(); + }); + + it('should render category names with test ids', () => { + render(); + const securityCategory = screen.getByTestId('plugin-insight-security'); + const qualityCategory = screen.getByTestId('plugin-insight-quality'); + + expect(securityCategory).toBeInTheDocument(); + expect(securityCategory).toHaveTextContent('Security'); + expect(qualityCategory).toBeInTheDocument(); + expect(qualityCategory).toHaveTextContent('Quality'); + }); + + it('should render individual insight items with test ids', async () => { + render(); + await userEvent.click(screen.getByText('Security')); + expect(screen.getByTestId('plugin-insight-item-signature')).toBeInTheDocument(); + expect(screen.getByTestId('plugin-insight-item-trackingscripts')).toBeInTheDocument(); + await userEvent.click(screen.getByText('Quality')); + expect(screen.getByTestId('plugin-insight-item-metadatavalid')).toBeInTheDocument(); + expect(screen.getByTestId('plugin-insight-item-code-rules')).toBeInTheDocument(); + }); + + it('should display correct icons for Excellent score level', () => { + render(); + + const securityCategory = screen.getByTestId('plugin-insight-security'); + const securityIcon = securityCategory.querySelector('[data-testid="excellent-icon"]'); + expect(securityIcon).toBeInTheDocument(); + }); + + it('should display correct icons for Poor score levels', () => { + // Test Poor level - should show exclamation-triangle + render(); + const poorCategory = screen.getByTestId('plugin-insight-quality'); + const poorIcon = poorCategory.querySelector('[data-testid="poor-icon"]'); + expect(poorIcon).toBeInTheDocument(); + }); + + it('should handle multiple items with different insight levels', async () => { + const multiLevelInsights: CatalogPluginInsights = { + id: 5, + name: 'multi-level-plugin', + version: '2.0.0', + insights: [ + { + name: 'quality', + scoreValue: 75, + scoreLevel: SCORE_LEVELS.GOOD, + items: [ + { + id: 'code-rules', + name: 'Info level item', + level: 'info' as InsightLevel, + }, + { + id: 'sdk-usage', + name: 'OK level item', + level: 'ok' as InsightLevel, + }, + { + id: 'jsMap', + name: 'Good level item', + level: 'good' as InsightLevel, + }, + { + id: 'gosec', + name: 'Warning level item', + level: 'warning' as InsightLevel, + }, + { + id: 'legacy-builder', + name: 'Danger level item', + level: 'danger' as InsightLevel, + }, + ], + }, + ], + }; + render(); + await userEvent.click(screen.getByText('Quality')); + expect(screen.getByText('Info level item')).toBeInTheDocument(); + expect(screen.getByText('OK level item')).toBeInTheDocument(); + expect(screen.getByText('Good level item')).toBeInTheDocument(); + expect(screen.getByText('Warning level item')).toBeInTheDocument(); + expect(screen.getByText('Danger level item')).toBeInTheDocument(); + }); +}); diff --git a/public/app/features/plugins/admin/components/PluginInsights.tsx b/public/app/features/plugins/admin/components/PluginInsights.tsx new file mode 100644 index 00000000000..805bcf926bf --- /dev/null +++ b/public/app/features/plugins/admin/components/PluginInsights.tsx @@ -0,0 +1,140 @@ +import { css } from '@emotion/css'; +import { capitalize } from 'lodash'; +import { useState } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Trans } from '@grafana/i18n'; +import { reportInteraction } from '@grafana/runtime'; +import { Stack, Text, TextLink, CollapsableSection, Tooltip, Icon, useStyles2, useTheme2 } from '@grafana/ui'; + +import { CatalogPluginInsights } from '../types'; + +type Props = { pluginInsights: CatalogPluginInsights | undefined }; + +const PLUGINS_INSIGHTS_OPENED_EVENT_NAME = 'plugins_insights_opened'; + +export function PluginInsights(props: Props): React.ReactElement | null { + const { pluginInsights } = props; + const styles = useStyles2(getStyles); + const theme = useTheme2(); + const [openInsights, setOpenInsights] = useState>({}); + + const handleInsightToggle = (insightName: string, isOpen: boolean) => { + if (isOpen) { + reportInteraction(PLUGINS_INSIGHTS_OPENED_EVENT_NAME, { insight: insightName }); + } + setOpenInsights((prev) => ({ ...prev, [insightName]: isOpen })); + }; + + const tooltipInfo = ( + + + + + + All relevant signals are present and verified + + + + + + + + One or more signals are missing or need attention + + + +
+ + + Do you find Plugin Insights usefull? Please share your feedback{' '} + + here + + . + + +
+ ); + + return ( + <> + + + + Plugin insights + + + + + + {pluginInsights?.insights.map((insightItem, index) => { + return ( + + handleInsightToggle(insightItem.name, isOpen)} + label={ + + {insightItem.scoreLevel === 'Excellent' ? ( + + ) : ( + + )} + + {capitalize(insightItem.name)} + + + } + contentClassName={styles.pluginInsightsItems} + > + + {insightItem.items.map((item, idx) => ( + + + {item.level === 'good' ? ( + + ) : ( + + )} + + + {item.name} + + + ))} + + + + ); + })} + + + ); +} + +export const getStyles = (theme: GrafanaTheme2) => { + return { + pluginVersionDetails: css({ wordBreak: 'break-word' }), + pluginInsightsItems: css({ marginLeft: '26px', paddingTop: '0 !important' }), + pluginInsightsTooltipSeparator: css({ + border: 'none', + borderTop: `1px solid ${theme.colors.border.medium}`, + margin: `${theme.spacing(1)} 0`, + }), + }; +}; diff --git a/public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts b/public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts index 9ced4f20a84..3625b687f7b 100644 --- a/public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts +++ b/public/app/features/plugins/admin/mocks/catalogPlugin.mock.ts @@ -34,6 +34,7 @@ export default { updatedAt: '2021-08-25T15:03:49.000Z', version: '4.2.2', error: undefined, + insights: { id: 1, name: 'alexanderzobnin-zabbix-app', version: '4.2.2', insights: [] }, details: { grafanaDependency: '>=8.0.0', pluginDependencies: [], @@ -381,6 +382,7 @@ export const datasourcePlugin = { angularDetected: false, isFullyInstalled: true, latestVersion: '1.20.0', + insights: { id: 2, name: 'grafana-redshift-datasource', version: '1.20.0', insights: [] }, details: { grafanaDependency: '>=8.0.0', pluginDependencies: [], diff --git a/public/app/features/plugins/admin/mocks/mockHelpers.ts b/public/app/features/plugins/admin/mocks/mockHelpers.ts index 6034e8860e9..d6e04186f77 100644 --- a/public/app/features/plugins/admin/mocks/mockHelpers.ts +++ b/public/app/features/plugins/admin/mocks/mockHelpers.ts @@ -31,6 +31,9 @@ export const getPluginsStateMock = (plugins: CatalogPlugin[] = []): ReducerState 'plugins/fetchDetails': { status: RequestStatus.Fulfilled, }, + 'plugins/fetchPluginInsights': { + status: RequestStatus.Fulfilled, + }, }, // Backward compatibility plugins: [], @@ -75,6 +78,11 @@ export const mockPluginApis = ({ return Promise.resolve({ items: versions }); } + // Mock plugin insights - return empty insights to avoid API call errors + if (path.includes('/insights')) { + return Promise.resolve({ id: 1, name: '', version: '', insights: [] }); + } + // Mock local plugin settings (installed) if necessary if (local && path === `${API_ROOT}/${local.id}/settings`) { return Promise.resolve(local); diff --git a/public/app/features/plugins/admin/state/actions.ts b/public/app/features/plugins/admin/state/actions.ts index 6be68111dd5..e9cf2d9d40d 100644 --- a/public/app/features/plugins/admin/state/actions.ts +++ b/public/app/features/plugins/admin/state/actions.ts @@ -13,6 +13,7 @@ import { getPluginErrors, getLocalPlugins, getPluginDetails, + getPluginInsights, installPlugin, uninstallPlugin, getInstancePlugins, @@ -165,6 +166,22 @@ export const fetchDetails = createAsyncThunk, stri } ); +export const fetchPluginInsights = createAsyncThunk, { id: string; version?: string }>( + `${STATE_PREFIX}/fetchPluginInsights`, + async ({ id, version }, thunkApi) => { + try { + const insights = await getPluginInsights(id, version); + + return { + id, + changes: { insights }, + }; + } catch (e) { + return thunkApi.rejectWithValue('Unknown error.'); + } + } +); + export const addPlugins = createAction(`${STATE_PREFIX}/addPlugins`); // 1. gets remote equivalents from the store (if there are any) @@ -265,7 +282,8 @@ export const panelPluginLoaded = createAction(`${STATE_PREFIX}/pane // TODO export const loadPanelPlugin = (id: string): ThunkResult> => { return async (dispatch, getStore) => { - let plugin = getStore().plugins.panels[id]; + const state = getStore(); + let plugin = state.plugins.panels[id]; if (!plugin) { plugin = await importPanelPlugin(id); diff --git a/public/app/features/plugins/admin/state/hooks.ts b/public/app/features/plugins/admin/state/hooks.ts index 2185ec99465..6eb47d7e1aa 100644 --- a/public/app/features/plugins/admin/state/hooks.ts +++ b/public/app/features/plugins/admin/state/hooks.ts @@ -6,7 +6,16 @@ import { useDispatch, useSelector } from 'app/types/store'; import { sortPlugins, Sorters, isPluginUpdatable } from '../helpers'; import { CatalogPlugin, PluginStatus } from '../types'; -import { fetchAll, fetchDetails, fetchRemotePlugins, install, uninstall, fetchAllLocal, unsetInstall } from './actions'; +import { + fetchAll, + fetchDetails, + fetchRemotePlugins, + install, + uninstall, + fetchAllLocal, + unsetInstall, + fetchPluginInsights, +} from './actions'; import { selectPlugins, selectById, @@ -44,13 +53,18 @@ export const useGetUpdatable = () => { }; }; -export const useGetSingle = (id: string): CatalogPlugin | undefined => { +export const useGetSingle = (id: string, version?: string): CatalogPlugin | undefined => { useFetchAll(); useFetchDetails(id); return useSelector((state) => selectById(state, id)); }; +export const useGetPluginInsights = (id: string, version: string | undefined): CatalogPlugin | undefined => { + useFetchPluginInsights(id, version); + return useSelector((state) => selectById(state, id)); +}; + export const useGetSingleLocalWithoutDetails = (id: string): CatalogPlugin | undefined => { useFetchAllLocal(); return useSelector((state) => selectById(state, id)); @@ -153,6 +167,17 @@ export const useFetchDetails = (id: string) => { }, [plugin]); // eslint-disable-line }; +export const useFetchPluginInsights = (id: string, version: string | undefined) => { + const dispatch = useDispatch(); + const plugin = useSelector((state) => selectById(state, id)); + const isNotFetching = !useSelector(selectIsRequestPending(fetchPluginInsights.typePrefix)); + const shouldFetch = isNotFetching && plugin && !plugin.insights && version; + + useEffect(() => { + shouldFetch && dispatch(fetchPluginInsights({ id, version })); + }, [plugin, version]); // eslint-disable-line +}; + export const useFetchDetailsLazy = () => { const dispatch = useDispatch(); diff --git a/public/app/features/plugins/admin/state/reducer.ts b/public/app/features/plugins/admin/state/reducer.ts index f2414a31405..e3d5bec5427 100644 --- a/public/app/features/plugins/admin/state/reducer.ts +++ b/public/app/features/plugins/admin/state/reducer.ts @@ -7,6 +7,7 @@ import { CatalogPlugin, ReducerState, RequestStatus } from '../types'; import { fetchDetails, + fetchPluginInsights, install, uninstall, loadPluginDashboards, @@ -63,6 +64,10 @@ const slice = createSlice({ .addCase(fetchDetails.fulfilled, (state, action) => { pluginsAdapter.updateOne(state.items, action.payload); }) + // Fetch Plugin Insights + .addCase(fetchPluginInsights.fulfilled, (state, action) => { + pluginsAdapter.updateOne(state.items, action.payload); + }) // Install .addCase(install.fulfilled, (state, action) => { pluginsAdapter.updateOne(state.items, action.payload); diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts index 3cc66bba0b9..df4114101b4 100644 --- a/public/app/features/plugins/admin/types.ts +++ b/public/app/features/plugins/admin/types.ts @@ -55,6 +55,7 @@ export interface CatalogPlugin extends WithAccessControlMetadata { updatedAt: string; installedVersion?: string; details?: CatalogPluginDetails; + insights?: CatalogPluginInsights; error?: PluginErrorCode; angularDetected?: boolean; // instance plugins may not be fully installed, which means a new instance @@ -90,6 +91,54 @@ export interface CatalogPluginDetails { screenshots?: Screenshots[] | null; } +export type InsightLevel = 'ok' | 'warning' | 'danger' | 'good' | 'info'; + +export const SCORE_LEVELS = { + EXCELLENT: 'Excellent', + GOOD: 'Good', + FAIR: 'Fair', + POOR: 'Poor', + CRITICAL: 'Critical', +} as const; + +export type ScoreLevel = (typeof SCORE_LEVELS)[keyof typeof SCORE_LEVELS]; + +export const INSIGHT_CATEGORIES = { + SECURITY: 'security', + QUALITY: 'quality', + PERFORMANCE: 'performance', +} as const; + +export const INSIGHT_LEVELS = { + GOOD: 'good', + OK: 'ok', + WARNING: 'warning', + DANGER: 'danger', + INFO: 'info', +} as const; + +export interface InsightItem { + id: string; + name: string; + description?: string; + level: InsightLevel; + link?: string; +} + +export interface InsightCategory { + name: string; + items: InsightItem[]; + scoreValue: number; + scoreLevel: ScoreLevel; +} + +export interface CatalogPluginInsights { + id: number; + name: string; + version: string; + insights: InsightCategory[]; +} + export interface CatalogPluginInfo { logos: { large: string; small: string }; keywords: string[]; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 00757c4251d..53abfa01d14 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -11406,6 +11406,12 @@ "latestReleaseDate": "Latest release date:", "latestVersion": "Latest Version", "license": "License", + "moreDetails": "Do you find Plugin Insights usefull? Please share your feedback <2>here.", + "pluginInsights": { + "header": "Plugin insights" + }, + "pluginInsightsSuccessTooltip": "All relevant signals are present and verified", + "pluginInsightsWarningTooltip": "One or more signals are missing or need attention", "raiseAnIssue": "Raise an issue", "reportAbuse": "Report a concern", "reportAbuseTooltip": "Report issues related to malicious or harmful plugins directly to Grafana Labs.", From ece38641cabf9dc0d4678053fe02b024968123e9 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Fri, 19 Dec 2025 13:48:53 +0100 Subject: [PATCH 252/297] Dashboards: Make sure to render dashboard links even if they are marked as "in controls menu" (#115381) links with type dashboard will now be visible. --- .../dashboard-scene/scene/dashboard-controls-menu/utils.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/dashboard-controls-menu/utils.tsx b/public/app/features/dashboard-scene/scene/dashboard-controls-menu/utils.tsx index 6514ea34ed9..04d0f7078c8 100644 --- a/public/app/features/dashboard-scene/scene/dashboard-controls-menu/utils.tsx +++ b/public/app/features/dashboard-scene/scene/dashboard-controls-menu/utils.tsx @@ -5,11 +5,7 @@ import { isDashboardDataLayerSetState } from '../DashboardDataLayerSet'; import { DashboardScene } from '../DashboardScene'; export function getDashboardControlsLinks(links: DashboardLink[]) { - // Dashboard links are not supported at the moment. - // Reason: nesting components causes issues since the inner dropdown is rendered in a portal, - // so clicking it closes the parent dropdown (the parent sees it as an overlay click, and the event cannot easily be intercepted, - // as it is in different HTML subtree). - return links.filter((link) => link.placement === 'inControlsMenu' && link.type !== 'dashboards'); + return links.filter((link) => link.placement === 'inControlsMenu'); } export function getDashboardControlsVariables(variables: SceneVariable[]) { From 9760eef62f5237c24bfc6681a0ec2457f547024d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Jim=C3=A9nez=20S=C3=A1nchez?= Date: Fri, 19 Dec 2025 15:11:35 +0100 Subject: [PATCH 253/297] Provisioning: fix multi-tenant and single-tenant authorization (#115435) * feat(auth): add ExtraAudience option to RoundTripper Add ExtraAudience option to RoundTripper to allow operators to include additional audiences (e.g., provisioning group) when connecting to the multitenant aggregator. This ensures tokens include both the target API server's audience and the provisioning group audience, which is required to pass the enforceManagerProperties check. - Add ExtraAudience RoundTripperOption - Improve documentation and comments - Add comprehensive test coverage * fix(operators): add ExtraAudience for dashboards/folders API servers Operators connecting to dashboards and folders API servers need to include the provisioning group audience in addition to the target API server's audience to pass the enforceManagerProperties check. * provisioning: fix settings/stats authorization for AccessPolicy identities The settings and stats endpoints were returning 403 for users accessing via ST->MT because the AccessPolicy identity was routed to the access checker, which doesn't know about these resources. This fix handles 'settings' and 'stats' resources before the access checker path, routing them to the role-based authorization that allows: - settings: Viewer role (read-only, needed by frontend) - stats: Admin role (can leak information) * fix: update BootstrapStep component to remove legacy storage handling and adjust resource counting logic - Removed legacy storage flag from useResourceStats hook in BootstrapStep. - Updated BootstrapStepResourceCounting to simplify rendering logic and removed target prop. - Adjusted tests to reflect changes in resource counting and rendering behavior. * Revert "fix: update BootstrapStep component to remove legacy storage handling and adjust resource counting logic" This reverts commit 148802cbb59722d48347f09ff7351dc747f272a6. * provisioning: allow any authenticated user for settings/stats endpoints These are read-only endpoints needed by the frontend: - settings: returns available repository types and configuration for the wizard - stats: returns resource counts Authentication is verified before reaching authorization, so any user who reaches these endpoints is already authenticated. Requiring specific org roles failed for AccessPolicy tokens which don't carry traditional roles. * provisioning: remove redundant admin role check from listFolderFiles The admin role check in listFolderFiles was redundant (route-level auth already handles access) and broken for AccessPolicy identities which don't have org roles. File access is controlled by the AccessClient as documented in the route-level authorization comment. * provisioning: add isAdminOrAccessPolicy helper for auth checks Consolidates authorization logic for provisioning endpoints: - Adds isAdminOrAccessPolicy() helper that allows admin users OR AccessPolicy identities - AccessPolicy identities (ST->MT flow) are trusted internal callers without org roles - Regular users must have admin role (matching frontend navtree restriction) Used in: authorizeSettings, authorizeStats, authorizeJobs, listFolderFiles * provisioning: consolidate auth helpers into allowForAdminsOrAccessPolicy Simplifies authorization by: - Adding isAccessPolicy() helper for AccessPolicy identity check - Adding allowForAdminsOrAccessPolicy() that returns Decision directly - Consolidating stats/settings/jobs into single switch case - Using consistent pattern in files.go * provisioning: require admin for files subresource at route level Aligns route-level authorization with handler-level check in listFolderFiles. Both now require admin role OR AccessPolicy identity for consistency. * provisioning: restructure authorization with role-based helpers Reorganizes authorization code for clarity: Role-based helpers (all support AccessPolicy for ST->MT flow): - allowForAdminsOrAccessPolicy: admin role required - allowForEditorsOrAccessPolicy: editor role required - allowForViewersOrAccessPolicy: viewer role required Repository subresources by role: - Admin: repository CRUD, test, files - Editor: jobs, resources, sync, history - Viewer: refs, status (GET only) Connection subresources by role: - Admin: connection CRUD - Viewer: status (GET only) * provisioning: move refs to admin-only refs subresource now requires admin role (or AccessPolicy). Updated documentation comments to reflect current permissions. * provisioning: add fine-grained permissions for connections Adds connection permissions following the same pattern as repositories: - provisioning.connections:create - provisioning.connections:read - provisioning.connections:write - provisioning.connections:delete Roles: - fixed:provisioning.connections:reader (granted to Admin) - fixed:provisioning.connections:writer (granted to Admin) * provisioning: remove non-existent sync subresource from auth The sync subresource doesn't exist - syncing is done via the jobs endpoint. Removed dead code from authorization switch case. * provisioning: use access checker for fine-grained permissions Refactors authorization to use b.access.Check() with verb-based checks: Repository subresources: - CRUD: uses actual verb (get/create/update/delete) - test: uses 'update' (write permission) - files/refs/resources/history/status: uses 'get' (read permission) - jobs: uses actual verb for jobs resource Connection subresources: - CRUD: uses actual verb - status: uses 'get' (read permission) The access checker maps verbs to actions defined in accesscontrol.go. Falls back to admin role for backwards compatibility. Also removes redundant admin check from listFolderFiles since authorization is now properly handled at route level. * provisioning: use verb constants instead of string literals Uses apiutils.VerbGet, apiutils.VerbUpdate instead of "get", "update". * provisioning: use access checker for jobs and historicjobs resources Jobs resource: uses actual verb (create/read/write/delete) HistoricJobs resource: read-only (historicjobs:read) * provisioning: allow viewers to access settings endpoint Settings is read-only and needed by multiple UI pages (not just admin pages). Stats remains admin-only. * provisioning: consolidate role-based resource authorization Extract isRoleBasedResource() and authorizeRoleBasedResource() helpers to avoid duplicating settings/stats resource checks in multiple places. * provisioning: use resource name constants instead of hardcoded strings Replace 'repositories', 'connections', 'jobs', 'historicjobs' with their corresponding ResourceInfo.GetName() constants. * provisioning: delegate file authorization to connector Route level: allow any authenticated user for files subresource Connector: check repositories:read only for directory listing Individual file CRUD: handled by DualReadWriter based on actual resource * provisioning: enhance authorization for files and jobs resources Updated file authorization to fall back to admin role for listing files. Introduced checkAccessForJobs function to manage job permissions, allowing editors to create and manage jobs while maintaining admin-only access for historic jobs. Improved error messaging for permission denials. * provisioning: refactor authorization with fine-grained permissions Authorization changes: - Use access checker with role-based fallback for backwards compatibility - Repositories/Connections: admin role fallback - Jobs: editor role fallback (editors can manage jobs) - HistoricJobs: admin role fallback (read-only) - Settings: viewer role (needed by multiple UI pages) - Stats: admin role Files subresource: - Route level allows any authenticated user - Directory listing checks repositories:read in connector - Individual file CRUD delegated to DualReadWriter Refactored checkAccessWithFallback to accept fallback role parameter. * provisioning: refactor access checker integration for improved authorization Updated the authorization logic to utilize the new access checker across various resources, including files and jobs. This change simplifies the permission checks by removing redundant identity retrieval and enhances error handling. The access checker now supports role-based fallbacks for admin and editor roles, ensuring backward compatibility while streamlining the authorization process for repository and connection subresources. * provisioning: remove legacy access checker tests and refactor access checker implementation Deleted the access_checker_test.go file to streamline the codebase and focus on the updated access checker implementation. Refactored the access checker to enhance clarity and maintainability, ensuring it supports role-based fallback behavior. Updated the access checker integration in the API builder to utilize the new fallback role configuration, improving authorization logic across resources. * refactor: split AccessChecker into TokenAccessChecker and SessionAccessChecker - Renamed NewMultiTenantAccessChecker -> NewTokenAccessChecker (uses AuthInfoFrom) - Renamed NewSingleTenantAccessChecker -> NewSessionAccessChecker (uses GetRequester) - Split into separate files with their own tests - Added mockery-generated mock for AccessChecker interface - Names now reflect identity source rather than deployment mode * fix: correct error message case and use accessWithAdmin for filesConnector - Fixed error message to use lowercase 'admin role is required' - Fixed filesConnector to use accessWithAdmin for proper role fallback - Formatted code * refactor: reduce cyclomatic complexity in filesConnector.Connect Split the Connect handler into smaller focused functions: - handleRequest: main request processing - createDualReadWriter: setup dependencies - parseRequestOptions: extract request options - handleDirectoryListing: GET directory requests - handleMethodRequest: route to method handlers - handleGet/handlePost/handlePut/handleDelete: method-specific logic - handleMove: move operation logic * security: remove blind TypeAccessPolicy bypass from access checkers Removed the code that bypassed authorization for TypeAccessPolicy identities. All identities now go through proper permission verification via the inner access checker, which will validate permissions from ServiceIdentityClaims. This addresses the security concern where TypeAccessPolicy was being trusted blindly without verifying whether the identity came from the wire or in-process. * feat: allow editors to access repository refs subresource Change refs authorization from admin to editor fallback so editors can view repository branches when pushing changes to dashboards/folders. - Split refs from other read-only subresources (resources, history, status) - refs now uses accessWithEditor instead of accessWithAdmin - Updated documentation comment to reflect authorization levels - Added integration test TestIntegrationProvisioning_RefsPermissions verifying editor access and viewer denial * tests: add authorization tests for missing provisioning API endpoints Add comprehensive authorization tests for: - Repository subresources (test, resources, history, status) - Connection status subresource - HistoricJobs resource - Settings and Stats resources All authorization paths are now covered by integration tests. * test: fix RefsPermissions test to use GitHub repository Use github-readonly.json.tmpl template instead of local folder, since refs endpoint requires a versioned repository that supports git operations. * chore: format test files * fix: make settings/stats authorization work in MT mode Update authorizeRoleBasedResource to check authlib.AuthInfoFrom(ctx) for AccessPolicy identity type in addition to identity.GetRequester(ctx). This ensures AccessPolicy identities are recognized in MT mode where identity.GetRequester may not set the identity type correctly. * fix: remove unused authorization helper functions Remove allowForAdminsOrAccessPolicy and allowForViewersOrAccessPolicy as they are no longer used after refactoring to use authorizeRoleBasedResource. * Fix AccessPolicy identity detection in ST authorizer - Add check for AccessPolicy identities via GetAuthID() in authorizeRoleBasedResource - Extended JWT may set identity type to TypeUser but AuthID is 'access-policy:...' - Forward user ID token in X-Grafana-Id header in RoundTripper for aggregator forwarding * Revert "Fix AccessPolicy identity detection in ST authorizer" This reverts commit 0f4885e503a633d5e78252842d8885d396ad1bab. * Add fine-grained permissions for settings and stats endpoints - Add provisioning.settings:read action (granted to Viewer role) - Add provisioning.stats:read action (granted to Admin role) - Add accessWithViewer to APIBuilder for Viewer role fallback - Use access checker for settings/stats authorization - Remove role-based authorization functions (isRoleBasedResource, authorizeRoleBasedResource) This makes settings and stats consistent with other provisioning resources and works properly in both ST and MT modes via the access checker. * Remove AUTHORIZATION_COVERAGE.md * Add provisioning resources to RBAC mapper - Add connections, settings, stats to provisioning.grafana.app mappings - Required for authz service to translate K8s verbs to legacy actions - Fixes 403 errors for settings/stats in MT mode * refactor: merge access checkers with original fallthrough behavior Merge tokenAccessChecker and sessionAccessChecker into a unified access checker that implements the original fallthrough behavior: 1. First try to get identity from access token (authlib.AuthInfoFrom) 2. If token exists AND (is TypeAccessPolicy OR useExclusivelyAccessCheckerForAuthz), use the access checker with token identity 3. If no token or conditions not met, fall back to session identity (identity.GetRequester) with optional role-based fallback This fixes the issue where settings/stats/connections endpoints were failing in MT mode because the tokenAccessChecker was returning an error when there was no auth info in context, instead of falling through to session-based authorization. The unified checker now properly handles: - MT mode: tries token first, falls back to session if no token - ST mode: only uses token for AccessPolicy identities, otherwise session - Role fallback: applies when configured and access checker denies * Revert "refactor: merge access checkers with original fallthrough behavior" This reverts commit 96451f948bf8d723845567d2a26e7ecffda483ba. * Grant settings view role to all * fix: use actual request verb for settings/stats authorization Use a.GetVerb() instead of hardcoded VerbGet for settings and stats authorization. When listing resources (hitting collection endpoint), the verb is 'list' not 'get', and this mismatch could cause issues with the RBAC service. * debug: add logging to access checkers for authorization debugging Add klog debug logs (V4 level) to token and session access checkers to help diagnose why settings/stats authorization is failing while connections works. * debug: improve access checker logging with grafana-app-sdk logger - Use grafana-app-sdk logging.FromContext instead of klog - Add error wrapping with resource.group format for better context - Log more details including folder, group, and allowed status - Log error.Error() for better error message visibility * chore: use generic log messages in access checkers * Revert "Grant settings view role to all" This reverts commit 3f5758cf3656b7e6c267e7a3e83d98da1949ab20. * fix: use request verb for historicjobs authorization The original role-based check allowed any verb for admins. To preserve this behavior with the access checker, we should pass the actual verb from the request instead of hardcoding VerbGet. --------- Co-authored-by: Charandas Batra --- apps/provisioning/pkg/auth/access_checker.go | 22 + .../pkg/auth/access_checker_mock.go | 135 ++++++ apps/provisioning/pkg/auth/round_tripper.go | 60 ++- .../pkg/auth/round_tripper_test.go | 23 +- .../pkg/auth/session_access_checker.go | 153 +++++++ .../pkg/auth/session_access_checker_test.go | 244 +++++++++++ .../pkg/auth/token_access_checker.go | 92 +++++ .../pkg/auth/token_access_checker_test.go | 137 ++++++ pkg/operators/provisioning/config.go | 2 +- .../apis/provisioning/accesscontrol.go | 88 ++++ pkg/registry/apis/provisioning/files.go | 391 ++++++++++-------- pkg/registry/apis/provisioning/register.go | 336 ++++++++------- .../apis/provisioning/resources/dualwriter.go | 49 +-- pkg/services/authz/rbac/mapper.go | 3 + .../connection_status_auth_test.go | 88 ++++ .../provisioning/historicjobs_auth_test.go | 95 +++++ .../repository_subresources_auth_test.go | 236 +++++++++++ .../apis/provisioning/repository_test.go | 63 +++ .../provisioning/settings_stats_auth_test.go | 104 +++++ 19 files changed, 1955 insertions(+), 366 deletions(-) create mode 100644 apps/provisioning/pkg/auth/access_checker.go create mode 100644 apps/provisioning/pkg/auth/access_checker_mock.go create mode 100644 apps/provisioning/pkg/auth/session_access_checker.go create mode 100644 apps/provisioning/pkg/auth/session_access_checker_test.go create mode 100644 apps/provisioning/pkg/auth/token_access_checker.go create mode 100644 apps/provisioning/pkg/auth/token_access_checker_test.go create mode 100644 pkg/tests/apis/provisioning/connection_status_auth_test.go create mode 100644 pkg/tests/apis/provisioning/historicjobs_auth_test.go create mode 100644 pkg/tests/apis/provisioning/repository_subresources_auth_test.go create mode 100644 pkg/tests/apis/provisioning/settings_stats_auth_test.go diff --git a/apps/provisioning/pkg/auth/access_checker.go b/apps/provisioning/pkg/auth/access_checker.go new file mode 100644 index 00000000000..841c5421f2a --- /dev/null +++ b/apps/provisioning/pkg/auth/access_checker.go @@ -0,0 +1,22 @@ +package auth + +import ( + "context" + + authlib "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/apimachinery/identity" +) + +//go:generate mockery --name AccessChecker --structname MockAccessChecker --inpackage --filename access_checker_mock.go --with-expecter + +// AccessChecker provides access control checks with optional role-based fallback. +type AccessChecker interface { + // Check performs an access check and returns nil if allowed, or an appropriate + // API error if denied. If req.Namespace is empty, it will be filled from the + // identity's namespace. + Check(ctx context.Context, req authlib.CheckRequest, folder string) error + + // WithFallbackRole returns an AccessChecker configured with the specified fallback role. + // Whether the fallback is actually applied depends on the implementation. + WithFallbackRole(role identity.RoleType) AccessChecker +} diff --git a/apps/provisioning/pkg/auth/access_checker_mock.go b/apps/provisioning/pkg/auth/access_checker_mock.go new file mode 100644 index 00000000000..d0f1cddd7fc --- /dev/null +++ b/apps/provisioning/pkg/auth/access_checker_mock.go @@ -0,0 +1,135 @@ +// Code generated by mockery v2.53.4. DO NOT EDIT. + +package auth + +import ( + context "context" + + identity "github.com/grafana/grafana/pkg/apimachinery/identity" + mock "github.com/stretchr/testify/mock" + + types "github.com/grafana/authlib/types" +) + +// MockAccessChecker is an autogenerated mock type for the AccessChecker type +type MockAccessChecker struct { + mock.Mock +} + +type MockAccessChecker_Expecter struct { + mock *mock.Mock +} + +func (_m *MockAccessChecker) EXPECT() *MockAccessChecker_Expecter { + return &MockAccessChecker_Expecter{mock: &_m.Mock} +} + +// Check provides a mock function with given fields: ctx, req, folder +func (_m *MockAccessChecker) Check(ctx context.Context, req types.CheckRequest, folder string) error { + ret := _m.Called(ctx, req, folder) + + if len(ret) == 0 { + panic("no return value specified for Check") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, types.CheckRequest, string) error); ok { + r0 = rf(ctx, req, folder) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockAccessChecker_Check_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Check' +type MockAccessChecker_Check_Call struct { + *mock.Call +} + +// Check is a helper method to define mock.On call +// - ctx context.Context +// - req types.CheckRequest +// - folder string +func (_e *MockAccessChecker_Expecter) Check(ctx interface{}, req interface{}, folder interface{}) *MockAccessChecker_Check_Call { + return &MockAccessChecker_Check_Call{Call: _e.mock.On("Check", ctx, req, folder)} +} + +func (_c *MockAccessChecker_Check_Call) Run(run func(ctx context.Context, req types.CheckRequest, folder string)) *MockAccessChecker_Check_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(types.CheckRequest), args[2].(string)) + }) + return _c +} + +func (_c *MockAccessChecker_Check_Call) Return(_a0 error) *MockAccessChecker_Check_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockAccessChecker_Check_Call) RunAndReturn(run func(context.Context, types.CheckRequest, string) error) *MockAccessChecker_Check_Call { + _c.Call.Return(run) + return _c +} + +// WithFallbackRole provides a mock function with given fields: role +func (_m *MockAccessChecker) WithFallbackRole(role identity.RoleType) AccessChecker { + ret := _m.Called(role) + + if len(ret) == 0 { + panic("no return value specified for WithFallbackRole") + } + + var r0 AccessChecker + if rf, ok := ret.Get(0).(func(identity.RoleType) AccessChecker); ok { + r0 = rf(role) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(AccessChecker) + } + } + + return r0 +} + +// MockAccessChecker_WithFallbackRole_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WithFallbackRole' +type MockAccessChecker_WithFallbackRole_Call struct { + *mock.Call +} + +// WithFallbackRole is a helper method to define mock.On call +// - role identity.RoleType +func (_e *MockAccessChecker_Expecter) WithFallbackRole(role interface{}) *MockAccessChecker_WithFallbackRole_Call { + return &MockAccessChecker_WithFallbackRole_Call{Call: _e.mock.On("WithFallbackRole", role)} +} + +func (_c *MockAccessChecker_WithFallbackRole_Call) Run(run func(role identity.RoleType)) *MockAccessChecker_WithFallbackRole_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(identity.RoleType)) + }) + return _c +} + +func (_c *MockAccessChecker_WithFallbackRole_Call) Return(_a0 AccessChecker) *MockAccessChecker_WithFallbackRole_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockAccessChecker_WithFallbackRole_Call) RunAndReturn(run func(identity.RoleType) AccessChecker) *MockAccessChecker_WithFallbackRole_Call { + _c.Call.Return(run) + return _c +} + +// NewMockAccessChecker creates a new instance of MockAccessChecker. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockAccessChecker(t interface { + mock.TestingT + Cleanup(func()) +}) *MockAccessChecker { + mock := &MockAccessChecker{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/apps/provisioning/pkg/auth/round_tripper.go b/apps/provisioning/pkg/auth/round_tripper.go index 0d2f1cb4ac4..f5da0d778f0 100644 --- a/apps/provisioning/pkg/auth/round_tripper.go +++ b/apps/provisioning/pkg/auth/round_tripper.go @@ -1,3 +1,4 @@ +// Package auth provides authentication utilities for the provisioning API. package auth import ( @@ -6,7 +7,6 @@ import ( "net/http" "github.com/grafana/authlib/authn" - "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" utilnet "k8s.io/apimachinery/pkg/util/net" ) @@ -15,29 +15,61 @@ type tokenExchanger interface { Exchange(ctx context.Context, req authn.TokenExchangeRequest) (*authn.TokenExchangeResponse, error) } -// RoundTripper injects an exchanged access token for the provisioning API into outgoing requests. -type RoundTripper struct { - client tokenExchanger - transport http.RoundTripper - audience string +// RoundTripperOption configures optional behavior for the RoundTripper. +type RoundTripperOption func(*RoundTripper) + +// ExtraAudience appends an additional audience to the token exchange request. +// +// This is primarily used by operators connecting to the multitenant aggregator, +// where the token must include both the target API server's audience (e.g., dashboards, +// folders) and the provisioning group audience. The provisioning group audience is +// required so that the token passes the enforceManagerProperties check, which prevents +// unauthorized updates to provisioned resources. +// +// Example: +// +// authrt.NewRoundTripper(client, rt, "dashboards.grafana.app", authrt.ExtraAudience("provisioning.grafana.app")) +func ExtraAudience(audience string) RoundTripperOption { + return func(rt *RoundTripper) { + rt.extraAudience = audience + } } -// NewRoundTripper constructs a RoundTripper that exchanges the provided token per request -// and forwards the request to the provided base transport. -func NewRoundTripper(tokenExchangeClient tokenExchanger, base http.RoundTripper, audience string) *RoundTripper { - return &RoundTripper{ +// RoundTripper is an http.RoundTripper that performs token exchange before each request. +// It exchanges the service's credentials for an access token scoped to the configured +// audience(s), then injects that token into the outgoing request's X-Access-Token header. +type RoundTripper struct { + client tokenExchanger + transport http.RoundTripper + audience string + extraAudience string +} + +// NewRoundTripper creates a RoundTripper that exchanges tokens for each outgoing request. +// +// Parameters: +// - tokenExchangeClient: the client used to exchange credentials for access tokens +// - base: the underlying transport to delegate requests to after token injection +// - audience: the primary audience for the token (typically the target API server's group) +// - opts: optional configuration (e.g., ExtraAudience to include additional audiences) +func NewRoundTripper(tokenExchangeClient tokenExchanger, base http.RoundTripper, audience string, opts ...RoundTripperOption) *RoundTripper { + rt := &RoundTripper{ client: tokenExchangeClient, transport: base, audience: audience, } + for _, opt := range opts { + opt(rt) + } + return rt } +// RoundTrip exchanges credentials for an access token and injects it into the request. +// The token is scoped to all configured audiences and the wildcard namespace ("*"). func (t *RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - // when we want to write resources with the provisioning API, the audience needs to include provisioning - // so that it passes the check in enforceManagerProperties, which prevents others from updating provisioned resources audiences := []string{t.audience} - if t.audience != v0alpha1.GROUP { - audiences = append(audiences, v0alpha1.GROUP) + if t.extraAudience != "" && t.extraAudience != t.audience { + audiences = append(audiences, t.extraAudience) } tokenResponse, err := t.client.Exchange(req.Context(), authn.TokenExchangeRequest{ diff --git a/apps/provisioning/pkg/auth/round_tripper_test.go b/apps/provisioning/pkg/auth/round_tripper_test.go index e3ae4b7b3d4..c1b2b81e17f 100644 --- a/apps/provisioning/pkg/auth/round_tripper_test.go +++ b/apps/provisioning/pkg/auth/round_tripper_test.go @@ -71,16 +71,29 @@ func TestRoundTripper_AudiencesAndNamespace(t *testing.T) { tests := []struct { name string audience string + extraAudience string wantAudiences []string }{ { - name: "adds group when custom audience", + name: "uses only provided audience by default", audience: "example-audience", + wantAudiences: []string{"example-audience"}, + }, + { + name: "uses only group audience by default", + audience: v0alpha1.GROUP, + wantAudiences: []string{v0alpha1.GROUP}, + }, + { + name: "extra audience adds provisioning group", + audience: "example-audience", + extraAudience: v0alpha1.GROUP, wantAudiences: []string{"example-audience", v0alpha1.GROUP}, }, { - name: "no duplicate when group audience", + name: "extra audience no duplicate when same as primary", audience: v0alpha1.GROUP, + extraAudience: v0alpha1.GROUP, wantAudiences: []string{v0alpha1.GROUP}, }, } @@ -88,11 +101,15 @@ func TestRoundTripper_AudiencesAndNamespace(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { fx := &fakeExchanger{resp: &authn.TokenExchangeResponse{Token: "abc123"}} + var opts []RoundTripperOption + if tt.extraAudience != "" { + opts = append(opts, ExtraAudience(tt.extraAudience)) + } tr := NewRoundTripper(fx, roundTripperFunc(func(_ *http.Request) (*http.Response, error) { rr := httptest.NewRecorder() rr.WriteHeader(http.StatusOK) return rr.Result(), nil - }), tt.audience) + }), tt.audience, opts...) req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://example", nil) resp, err := tr.RoundTrip(req) diff --git a/apps/provisioning/pkg/auth/session_access_checker.go b/apps/provisioning/pkg/auth/session_access_checker.go new file mode 100644 index 00000000000..1bc6a1b7218 --- /dev/null +++ b/apps/provisioning/pkg/auth/session_access_checker.go @@ -0,0 +1,153 @@ +package auth + +import ( + "context" + "fmt" + "strings" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + + authlib "github.com/grafana/authlib/types" + "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana/pkg/apimachinery/identity" +) + +// sessionAccessChecker implements AccessChecker using Grafana session identity. +type sessionAccessChecker struct { + inner authlib.AccessChecker + fallbackRole identity.RoleType +} + +// NewSessionAccessChecker creates an AccessChecker that gets identity from Grafana +// sessions via GetRequester(ctx). Supports optional role-based fallback via +// WithFallbackRole for backwards compatibility. +func NewSessionAccessChecker(inner authlib.AccessChecker) AccessChecker { + return &sessionAccessChecker{ + inner: inner, + fallbackRole: "", + } +} + +// WithFallbackRole returns a new AccessChecker with the specified fallback role. +func (c *sessionAccessChecker) WithFallbackRole(role identity.RoleType) AccessChecker { + return &sessionAccessChecker{ + inner: c.inner, + fallbackRole: role, + } +} + +// Check performs an access check with optional role-based fallback. +// Returns nil if access is allowed, or an appropriate API error if denied. +func (c *sessionAccessChecker) Check(ctx context.Context, req authlib.CheckRequest, folder string) error { + logger := logging.FromContext(ctx).With("logger", "sessionAccessChecker") + + // Get identity from Grafana session + requester, err := identity.GetRequester(ctx) + if err != nil { + logger.Debug("failed to get requester", + "resource", req.Resource, + "verb", req.Verb, + "error", err.Error(), + ) + return apierrors.NewUnauthorized(fmt.Sprintf("failed to get requester: %v", err)) + } + + logger.Debug("checking access", + "identityType", requester.GetIdentityType(), + "orgRole", requester.GetOrgRole(), + "namespace", requester.GetNamespace(), + "resource", req.Resource, + "verb", req.Verb, + "group", req.Group, + "name", req.Name, + "folder", folder, + "fallbackRole", c.fallbackRole, + ) + + // Fill in namespace from identity if not provided + if req.Namespace == "" { + req.Namespace = requester.GetNamespace() + } + + // Perform the access check + rsp, err := c.inner.Check(ctx, requester, req, folder) + + // Build the GroupResource for error messages + gr := schema.GroupResource{Group: req.Group, Resource: req.Resource} + + // No fallback configured, return result directly + if c.fallbackRole == "" { + if err != nil { + logger.Debug("access check error (no fallback)", + "resource", req.Resource, + "verb", req.Verb, + "error", err.Error(), + ) + return apierrors.NewForbidden(gr, req.Name, fmt.Errorf("%s.%s is forbidden: %w", req.Resource, req.Group, err)) + } + if !rsp.Allowed { + logger.Debug("access check denied (no fallback)", + "resource", req.Resource, + "verb", req.Verb, + "group", req.Group, + "allowed", rsp.Allowed, + ) + return apierrors.NewForbidden(gr, req.Name, fmt.Errorf("permission denied")) + } + logger.Debug("access allowed", + "resource", req.Resource, + "verb", req.Verb, + ) + return nil + } + + // Fallback is configured - apply fallback logic + if err != nil { + if requester.GetOrgRole().Includes(c.fallbackRole) { + logger.Debug("access allowed via role fallback (after error)", + "resource", req.Resource, + "verb", req.Verb, + "fallbackRole", c.fallbackRole, + "orgRole", requester.GetOrgRole(), + ) + return nil // Fallback succeeded + } + logger.Debug("access check error (fallback failed)", + "resource", req.Resource, + "verb", req.Verb, + "error", err.Error(), + "fallbackRole", c.fallbackRole, + "orgRole", requester.GetOrgRole(), + ) + return apierrors.NewForbidden(gr, req.Name, fmt.Errorf("%s.%s is forbidden: %w", req.Resource, req.Group, err)) + } + + if rsp.Allowed { + logger.Debug("access allowed", + "resource", req.Resource, + "verb", req.Verb, + ) + return nil + } + + // Fall back to role for backwards compatibility + if requester.GetOrgRole().Includes(c.fallbackRole) { + logger.Debug("access allowed via role fallback", + "resource", req.Resource, + "verb", req.Verb, + "fallbackRole", c.fallbackRole, + "orgRole", requester.GetOrgRole(), + ) + return nil // Fallback succeeded + } + + logger.Debug("access denied (fallback role not met)", + "resource", req.Resource, + "verb", req.Verb, + "group", req.Group, + "fallbackRole", c.fallbackRole, + "orgRole", requester.GetOrgRole(), + ) + return apierrors.NewForbidden(gr, req.Name, fmt.Errorf("%s role is required", strings.ToLower(string(c.fallbackRole)))) +} diff --git a/apps/provisioning/pkg/auth/session_access_checker_test.go b/apps/provisioning/pkg/auth/session_access_checker_test.go new file mode 100644 index 00000000000..1e99a6e46db --- /dev/null +++ b/apps/provisioning/pkg/auth/session_access_checker_test.go @@ -0,0 +1,244 @@ +package auth + +import ( + "context" + "errors" + "testing" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + + authlib "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/user" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// mockRequester implements identity.Requester for testing. +type mockRequester struct { + identity.Requester + orgRole identity.RoleType + identityType authlib.IdentityType + namespace string +} + +func (m *mockRequester) GetOrgRole() identity.RoleType { + return m.orgRole +} + +func (m *mockRequester) GetIdentityType() authlib.IdentityType { + return m.identityType +} + +func (m *mockRequester) GetNamespace() string { + return m.namespace +} + +func TestSessionAccessChecker_Check(t *testing.T) { + ctx := context.Background() + req := authlib.CheckRequest{ + Verb: "get", + Group: "provisioning.grafana.app", + Resource: "repositories", + Name: "test-repo", + Namespace: "default", + } + + tests := []struct { + name string + fallbackRole identity.RoleType + innerResponse authlib.CheckResponse + innerErr error + requester *mockRequester + expectAllow bool + }{ + { + name: "allowed by checker", + fallbackRole: identity.RoleAdmin, + innerResponse: authlib.CheckResponse{Allowed: true}, + requester: &mockRequester{orgRole: identity.RoleViewer, identityType: authlib.TypeUser}, + expectAllow: true, + }, + { + name: "denied by checker, fallback to admin role succeeds", + fallbackRole: identity.RoleAdmin, + innerResponse: authlib.CheckResponse{Allowed: false}, + requester: &mockRequester{orgRole: identity.RoleAdmin, identityType: authlib.TypeUser}, + expectAllow: true, + }, + { + name: "denied by checker, fallback to admin role fails for viewer", + fallbackRole: identity.RoleAdmin, + innerResponse: authlib.CheckResponse{Allowed: false}, + requester: &mockRequester{orgRole: identity.RoleViewer, identityType: authlib.TypeUser}, + expectAllow: false, + }, + { + name: "error from checker, fallback to admin role succeeds", + fallbackRole: identity.RoleAdmin, + innerErr: errors.New("access check failed"), + requester: &mockRequester{orgRole: identity.RoleAdmin, identityType: authlib.TypeUser}, + expectAllow: true, + }, + { + name: "error from checker, fallback fails for viewer", + fallbackRole: identity.RoleAdmin, + innerErr: errors.New("access check failed"), + requester: &mockRequester{orgRole: identity.RoleViewer, identityType: authlib.TypeUser}, + expectAllow: false, + }, + { + name: "denied, editor fallback succeeds for editor", + fallbackRole: identity.RoleEditor, + innerResponse: authlib.CheckResponse{Allowed: false}, + requester: &mockRequester{orgRole: identity.RoleEditor, identityType: authlib.TypeUser}, + expectAllow: true, + }, + { + name: "denied, editor fallback fails for viewer", + fallbackRole: identity.RoleEditor, + innerResponse: authlib.CheckResponse{Allowed: false}, + requester: &mockRequester{orgRole: identity.RoleViewer, identityType: authlib.TypeUser}, + expectAllow: false, + }, + { + name: "no fallback configured, denied stays denied", + fallbackRole: "", // no fallback + innerResponse: authlib.CheckResponse{Allowed: false}, + requester: &mockRequester{orgRole: identity.RoleAdmin, identityType: authlib.TypeUser}, + expectAllow: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mock := &mockInnerAccessChecker{ + response: tt.innerResponse, + err: tt.innerErr, + } + + checker := NewSessionAccessChecker(mock) + if tt.fallbackRole != "" { + checker = checker.WithFallbackRole(tt.fallbackRole) + } + + // Add requester to context + testCtx := identity.WithRequester(ctx, tt.requester) + + err := checker.Check(testCtx, req, "") + + if tt.expectAllow { + require.NoError(t, err) + } else { + require.Error(t, err) + assert.True(t, apierrors.IsForbidden(err), "expected Forbidden error, got: %v", err) + } + }) + } +} + +func TestSessionAccessChecker_NoRequester(t *testing.T) { + mock := &mockInnerAccessChecker{ + response: authlib.CheckResponse{Allowed: true}, + } + + checker := NewSessionAccessChecker(mock) + err := checker.Check(context.Background(), authlib.CheckRequest{}, "") + + require.Error(t, err) + assert.True(t, apierrors.IsUnauthorized(err), "expected Unauthorized error") +} + +func TestSessionAccessChecker_WithFallbackRole_ImmutableOriginal(t *testing.T) { + mock := &mockInnerAccessChecker{ + response: authlib.CheckResponse{Allowed: false}, + } + + original := NewSessionAccessChecker(mock) + withAdmin := original.WithFallbackRole(identity.RoleAdmin) + withEditor := original.WithFallbackRole(identity.RoleEditor) + + ctx := identity.WithRequester(context.Background(), &mockRequester{ + orgRole: identity.RoleEditor, + identityType: authlib.TypeUser, + }) + + req := authlib.CheckRequest{} + + // Original should deny (no fallback) + err := original.Check(ctx, req, "") + require.Error(t, err, "original should deny without fallback") + + // WithAdmin should deny for editor + err = withAdmin.Check(ctx, req, "") + require.Error(t, err, "admin fallback should deny for editor") + + // WithEditor should allow for editor + err = withEditor.Check(ctx, req, "") + require.NoError(t, err, "editor fallback should allow for editor") +} + +func TestSessionAccessChecker_WithFallbackRole_ChainedCalls(t *testing.T) { + mock := &mockInnerAccessChecker{ + response: authlib.CheckResponse{Allowed: false}, + } + + // Ensure chained WithFallbackRole calls work correctly + checker := NewSessionAccessChecker(mock). + WithFallbackRole(identity.RoleAdmin). + WithFallbackRole(identity.RoleEditor) // This should override admin + + ctx := identity.WithRequester(context.Background(), &mockRequester{ + orgRole: identity.RoleEditor, + identityType: authlib.TypeUser, + }) + + err := checker.Check(ctx, authlib.CheckRequest{}, "") + require.NoError(t, err, "last fallback (editor) should be used") +} + +func TestSessionAccessChecker_RealSignedInUser(t *testing.T) { + mock := &mockInnerAccessChecker{ + response: authlib.CheckResponse{Allowed: false}, + } + + checker := NewSessionAccessChecker(mock).WithFallbackRole(identity.RoleAdmin) + + // Use a real SignedInUser + signedInUser := &user.SignedInUser{ + UserID: 1, + OrgID: 1, + OrgRole: identity.RoleAdmin, + } + + ctx := identity.WithRequester(context.Background(), signedInUser) + + err := checker.Check(ctx, authlib.CheckRequest{}, "") + require.NoError(t, err, "admin user should be allowed via fallback") +} + +func TestSessionAccessChecker_FillsNamespace(t *testing.T) { + mock := &mockInnerAccessChecker{ + response: authlib.CheckResponse{Allowed: true}, + } + + checker := NewSessionAccessChecker(mock) + + ctx := identity.WithRequester(context.Background(), &mockRequester{ + orgRole: identity.RoleAdmin, + identityType: authlib.TypeUser, + namespace: "org-123", + }) + + // Request without namespace + req := authlib.CheckRequest{ + Verb: "get", + Group: "provisioning.grafana.app", + Resource: "repositories", + Name: "test-repo", + // Namespace intentionally empty + } + + err := checker.Check(ctx, req, "") + require.NoError(t, err) +} diff --git a/apps/provisioning/pkg/auth/token_access_checker.go b/apps/provisioning/pkg/auth/token_access_checker.go new file mode 100644 index 00000000000..8df833d7a34 --- /dev/null +++ b/apps/provisioning/pkg/auth/token_access_checker.go @@ -0,0 +1,92 @@ +package auth + +import ( + "context" + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + + authlib "github.com/grafana/authlib/types" + "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana/pkg/apimachinery/identity" +) + +// tokenAccessChecker implements AccessChecker using access tokens from context. +type tokenAccessChecker struct { + inner authlib.AccessChecker +} + +// NewTokenAccessChecker creates an AccessChecker that gets identity from access tokens +// via AuthInfoFrom(ctx). Role-based fallback is not supported. +func NewTokenAccessChecker(inner authlib.AccessChecker) AccessChecker { + return &tokenAccessChecker{inner: inner} +} + +// WithFallbackRole returns the same checker since fallback is not supported. +func (c *tokenAccessChecker) WithFallbackRole(_ identity.RoleType) AccessChecker { + return c +} + +// Check performs an access check using AuthInfo from context. +// Returns nil if access is allowed, or an appropriate API error if denied. +func (c *tokenAccessChecker) Check(ctx context.Context, req authlib.CheckRequest, folder string) error { + logger := logging.FromContext(ctx).With("logger", "tokenAccessChecker") + + // Get identity from access token in context + id, ok := authlib.AuthInfoFrom(ctx) + if !ok { + logger.Debug("no auth info in context", + "resource", req.Resource, + "verb", req.Verb, + "namespace", req.Namespace, + ) + return apierrors.NewUnauthorized("no auth info in context") + } + + logger.Debug("checking access", + "identityType", id.GetIdentityType(), + "namespace", id.GetNamespace(), + "resource", req.Resource, + "verb", req.Verb, + "group", req.Group, + "name", req.Name, + "folder", folder, + ) + + // Fill in namespace from identity if not provided + if req.Namespace == "" { + req.Namespace = id.GetNamespace() + } + + // Perform the access check + rsp, err := c.inner.Check(ctx, id, req, folder) + + // Build the GroupResource for error messages + gr := schema.GroupResource{Group: req.Group, Resource: req.Resource} + + if err != nil { + logger.Debug("access check error", + "resource", req.Resource, + "verb", req.Verb, + "error", err.Error(), + ) + return apierrors.NewForbidden(gr, req.Name, fmt.Errorf("%s.%s is forbidden: %w", req.Resource, req.Group, err)) + } + if !rsp.Allowed { + logger.Debug("access check denied", + "resource", req.Resource, + "verb", req.Verb, + "group", req.Group, + "identityType", id.GetIdentityType(), + "allowed", rsp.Allowed, + ) + return apierrors.NewForbidden(gr, req.Name, fmt.Errorf("permission denied")) + } + + logger.Debug("access allowed", + "resource", req.Resource, + "verb", req.Verb, + ) + return nil +} diff --git a/apps/provisioning/pkg/auth/token_access_checker_test.go b/apps/provisioning/pkg/auth/token_access_checker_test.go new file mode 100644 index 00000000000..bce0d3a77a0 --- /dev/null +++ b/apps/provisioning/pkg/auth/token_access_checker_test.go @@ -0,0 +1,137 @@ +package auth + +import ( + "context" + "errors" + "testing" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + + authlib "github.com/grafana/authlib/types" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTokenAccessChecker_Check(t *testing.T) { + req := authlib.CheckRequest{ + Verb: "get", + Group: "provisioning.grafana.app", + Resource: "repositories", + Name: "test-repo", + Namespace: "default", + } + + tests := []struct { + name string + innerResponse authlib.CheckResponse + innerErr error + authInfo *identity.StaticRequester + expectAllow bool + }{ + { + name: "allowed by checker", + innerResponse: authlib.CheckResponse{Allowed: true}, + authInfo: &identity.StaticRequester{Type: authlib.TypeUser}, + expectAllow: true, + }, + { + name: "denied by checker", + innerResponse: authlib.CheckResponse{Allowed: false}, + authInfo: &identity.StaticRequester{Type: authlib.TypeUser}, + expectAllow: false, + }, + { + name: "error from checker", + innerErr: errors.New("access check failed"), + authInfo: &identity.StaticRequester{Type: authlib.TypeUser}, + expectAllow: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mock := &mockInnerAccessChecker{ + response: tt.innerResponse, + err: tt.innerErr, + } + + checker := NewTokenAccessChecker(mock) + + // Add auth info to context + testCtx := authlib.WithAuthInfo(context.Background(), tt.authInfo) + + err := checker.Check(testCtx, req, "") + + if tt.expectAllow { + require.NoError(t, err) + } else { + require.Error(t, err) + assert.True(t, apierrors.IsForbidden(err), "expected Forbidden error, got: %v", err) + } + }) + } +} + +func TestTokenAccessChecker_NoAuthInfo(t *testing.T) { + mock := &mockInnerAccessChecker{ + response: authlib.CheckResponse{Allowed: true}, + } + + checker := NewTokenAccessChecker(mock) + err := checker.Check(context.Background(), authlib.CheckRequest{}, "") + + require.Error(t, err) + assert.True(t, apierrors.IsUnauthorized(err), "expected Unauthorized error") +} + +func TestTokenAccessChecker_WithFallbackRole_IsNoOp(t *testing.T) { + mock := &mockInnerAccessChecker{ + response: authlib.CheckResponse{Allowed: false}, + } + + checker := NewTokenAccessChecker(mock) + checkerWithFallback := checker.WithFallbackRole(identity.RoleAdmin) + + // They should be the same instance + assert.Same(t, checker, checkerWithFallback, "WithFallbackRole should return same instance") +} + +func TestTokenAccessChecker_FillsNamespace(t *testing.T) { + mock := &mockInnerAccessChecker{ + response: authlib.CheckResponse{Allowed: true}, + } + + checker := NewTokenAccessChecker(mock) + + ctx := authlib.WithAuthInfo(context.Background(), &identity.StaticRequester{ + Type: authlib.TypeUser, + Namespace: "org-123", + }) + + // Request without namespace + req := authlib.CheckRequest{ + Verb: "get", + Group: "provisioning.grafana.app", + Resource: "repositories", + Name: "test-repo", + // Namespace intentionally empty + } + + err := checker.Check(ctx, req, "") + require.NoError(t, err) +} + +// mockInnerAccessChecker implements authlib.AccessChecker for testing. +type mockInnerAccessChecker struct { + response authlib.CheckResponse + err error +} + +func (m *mockInnerAccessChecker) Check(_ context.Context, _ authlib.AuthInfo, _ authlib.CheckRequest, _ string) (authlib.CheckResponse, error) { + return m.response, m.err +} + +func (m *mockInnerAccessChecker) Compile(_ context.Context, _ authlib.AuthInfo, _ authlib.ListRequest) (authlib.ItemChecker, authlib.Zookie, error) { + return nil, nil, nil +} diff --git a/pkg/operators/provisioning/config.go b/pkg/operators/provisioning/config.go index 8e496e5e556..05552e56095 100644 --- a/pkg/operators/provisioning/config.go +++ b/pkg/operators/provisioning/config.go @@ -178,7 +178,7 @@ func setupFromConfig(cfg *setting.Cfg, registry prometheus.Registerer) (controll APIPath: "/apis", Host: url, WrapTransport: transport.WrapperFunc(func(rt http.RoundTripper) http.RoundTripper { - return authrt.NewRoundTripper(tokenExchangeClient, rt, group) + return authrt.NewRoundTripper(tokenExchangeClient, rt, group, authrt.ExtraAudience(provisioning.GROUP)) }), Transport: &http.Transport{ MaxConnsPerHost: 100, diff --git a/pkg/registry/apis/provisioning/accesscontrol.go b/pkg/registry/apis/provisioning/accesscontrol.go index e56ab7b06e2..755eabb60da 100644 --- a/pkg/registry/apis/provisioning/accesscontrol.go +++ b/pkg/registry/apis/provisioning/accesscontrol.go @@ -12,6 +12,12 @@ const ( ActionProvisioningRepositoriesRead = "provisioning.repositories:read" // GET + LIST. ActionProvisioningRepositoriesDelete = "provisioning.repositories:delete" // DELETE. + // Connections + ActionProvisioningConnectionsCreate = "provisioning.connections:create" // CREATE. + ActionProvisioningConnectionsWrite = "provisioning.connections:write" // UPDATE. + ActionProvisioningConnectionsRead = "provisioning.connections:read" // GET + LIST. + ActionProvisioningConnectionsDelete = "provisioning.connections:delete" // DELETE. + // Jobs ActionProvisioningJobsCreate = "provisioning.jobs:create" // CREATE. ActionProvisioningJobsWrite = "provisioning.jobs:write" // UPDATE. @@ -20,6 +26,12 @@ const ( // Historic Jobs ActionProvisioningHistoricJobsRead = "provisioning.historicjobs:read" // GET + LIST. + + // Settings (read-only, needed by multiple UI pages) + ActionProvisioningSettingsRead = "provisioning.settings:read" // GET + LIST. + + // Stats (read-only, admin-only) + ActionProvisioningStatsRead = "provisioning.stats:read" // GET + LIST. ) func registerAccessControlRoles(service accesscontrol.Service) error { @@ -63,6 +75,46 @@ func registerAccessControlRoles(service accesscontrol.Service) error { Grants: []string{string(org.RoleAdmin)}, } + // Connections + connectionsReader := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:provisioning.connections:reader", + DisplayName: "Connections Reader", + Description: "Read and list provisioning connections.", + Group: "Provisioning", + Permissions: []accesscontrol.Permission{ + { + Action: ActionProvisioningConnectionsRead, + }, + }, + }, + Grants: []string{string(org.RoleAdmin)}, + } + + connectionsWriter := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:provisioning.connections:writer", + DisplayName: "Connections Writer", + Description: "Create, update and delete provisioning connections.", + Group: "Provisioning", + Permissions: []accesscontrol.Permission{ + { + Action: ActionProvisioningConnectionsCreate, + }, + { + Action: ActionProvisioningConnectionsRead, + }, + { + Action: ActionProvisioningConnectionsWrite, + }, + { + Action: ActionProvisioningConnectionsDelete, + }, + }, + }, + Grants: []string{string(org.RoleAdmin)}, + } + // Jobs jobsReader := accesscontrol.RoleRegistration{ Role: accesscontrol.RoleDTO{ @@ -119,11 +171,47 @@ func registerAccessControlRoles(service accesscontrol.Service) error { Grants: []string{string(org.RoleAdmin)}, } + // Settings - granted to Viewer (accessible by all logged-in users) + settingsReader := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:provisioning.settings:reader", + DisplayName: "Settings Reader", + Description: "Read provisioning settings.", + Group: "Provisioning", + Permissions: []accesscontrol.Permission{ + { + Action: ActionProvisioningSettingsRead, + }, + }, + }, + Grants: []string{string(org.RoleViewer)}, + } + + // Stats - granted to Admin only + statsReader := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Name: "fixed:provisioning.stats:reader", + DisplayName: "Stats Reader", + Description: "Read provisioning stats.", + Group: "Provisioning", + Permissions: []accesscontrol.Permission{ + { + Action: ActionProvisioningStatsRead, + }, + }, + }, + Grants: []string{string(org.RoleAdmin)}, + } + return service.DeclareFixedRoles( repositoriesReader, repositoriesWriter, + connectionsReader, + connectionsWriter, jobsReader, jobsWriter, historicJobsReader, + settingsReader, + statsReader, ) } diff --git a/pkg/registry/apis/provisioning/files.go b/pkg/registry/apis/provisioning/files.go index ad9bc4bc472..a418860caac 100644 --- a/pkg/registry/apis/provisioning/files.go +++ b/pkg/registry/apis/provisioning/files.go @@ -13,9 +13,10 @@ import ( authlib "github.com/grafana/authlib/types" "github.com/grafana/grafana-app-sdk/logging" provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + "github.com/grafana/grafana/apps/provisioning/pkg/auth" "github.com/grafana/grafana/apps/provisioning/pkg/repository" "github.com/grafana/grafana/apps/provisioning/pkg/safepath" - "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" ) @@ -26,12 +27,12 @@ const ( type filesConnector struct { getter RepoGetter - access authlib.AccessChecker + access auth.AccessChecker parsers resources.ParserFactory clients resources.ClientFactory } -func NewFilesConnector(getter RepoGetter, parsers resources.ParserFactory, clients resources.ClientFactory, access authlib.AccessChecker) *filesConnector { +func NewFilesConnector(getter RepoGetter, parsers resources.ParserFactory, clients resources.ClientFactory, access auth.AccessChecker) *filesConnector { return &filesConnector{getter: getter, parsers: parsers, clients: clients, access: access} } @@ -74,179 +75,233 @@ func (c *filesConnector) Connect(ctx context.Context, name string, opts runtime. ctx = logging.Context(ctx, logger) return WithTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - repo, err := c.getRepo(ctx, r.Method, name) - if err != nil { - logger.Debug("failed to find repository", "error", err) - responder.Error(err) - return - } - - readWriter, ok := repo.(repository.ReaderWriter) - if !ok { - responder.Error(apierrors.NewBadRequest("repository does not support read-writing")) - return - } - - parser, err := c.parsers.GetParser(ctx, readWriter) - if err != nil { - responder.Error(fmt.Errorf("failed to get parser: %w", err)) - return - } - - clients, err := c.clients.Clients(ctx, repo.Config().Namespace) - if err != nil { - responder.Error(fmt.Errorf("failed to get clients: %w", err)) - return - } - - folderClient, err := clients.Folder(ctx) - if err != nil { - responder.Error(fmt.Errorf("failed to get folder client: %w", err)) - return - } - folders := resources.NewFolderManager(readWriter, folderClient, resources.NewEmptyFolderTree()) - dualReadWriter := resources.NewDualReadWriter(readWriter, parser, folders, c.access) - query := r.URL.Query() - opts := resources.DualWriteOptions{ - Ref: query.Get("ref"), - Message: query.Get("message"), - SkipDryRun: query.Get("skipDryRun") == "true", - OriginalPath: query.Get("originalPath"), - Branch: repo.Config().Branch(), - } - logger := logger.With("url", r.URL.Path, "ref", opts.Ref, "message", opts.Message) - ctx := logging.Context(r.Context(), logger) - - opts.Path, err = pathAfterPrefix(r.URL.Path, fmt.Sprintf("/%s/files", name)) - if err != nil { - responder.Error(apierrors.NewBadRequest(err.Error())) - return - } - - if err := resources.IsPathSupported(opts.Path); err != nil { - responder.Error(apierrors.NewBadRequest(err.Error())) - return - } - - isDir := safepath.IsDir(opts.Path) - if r.Method == http.MethodGet && isDir { - files, err := c.listFolderFiles(ctx, opts.Path, opts.Ref, readWriter) - if err != nil { - responder.Error(err) - return - } - - responder.Object(http.StatusOK, files) - return - } - - if opts.Path == "" { - responder.Error(apierrors.NewBadRequest("missing request path")) - return - } - - var obj *provisioning.ResourceWrapper - code := http.StatusOK - switch r.Method { - case http.MethodGet: - resource, err := dualReadWriter.Read(ctx, opts.Path, opts.Ref) - if err != nil { - respondWithError(responder, err) - return - } - obj = resource.AsResourceWrapper() - case http.MethodPost: - // Check if this is a move operation first (originalPath query parameter is present) - if opts.OriginalPath != "" { - // For move operations, only read body for file moves (not directory moves) - if !isDir { - opts.Data, err = readBody(r, filesMaxBodySize) - if err != nil { - responder.Error(err) - return - } - } - - resource, err := dualReadWriter.MoveResource(ctx, opts) - if err != nil { - respondWithError(responder, err) - return - } - obj = resource.AsResourceWrapper() - } else if isDir { - obj, err = dualReadWriter.CreateFolder(ctx, opts) - } else { - opts.Data, err = readBody(r, filesMaxBodySize) - if err != nil { - responder.Error(err) - return - } - - var resource *resources.ParsedResource - resource, err = dualReadWriter.CreateResource(ctx, opts) - if err != nil { - respondWithError(responder, err) - return - } - obj = resource.AsResourceWrapper() - } - case http.MethodPut: - // TODO: document in API specification - if isDir { - err = apierrors.NewMethodNotSupported(provisioning.RepositoryResourceInfo.GroupResource(), r.Method) - } else { - opts.Data, err = readBody(r, filesMaxBodySize) - if err != nil { - responder.Error(err) - return - } - - resource, err := dualReadWriter.UpdateResource(ctx, opts) - if err != nil { - respondWithError(responder, err) - return - } - obj = resource.AsResourceWrapper() - } - case http.MethodDelete: - resource, err := dualReadWriter.Delete(ctx, opts) - if err != nil { - respondWithError(responder, err) - return - } - obj = resource.AsResourceWrapper() - default: - err = apierrors.NewMethodNotSupported(provisioning.RepositoryResourceInfo.GroupResource(), r.Method) - } - - if err != nil { - logger.Debug("got an error after processing request", "error", err) - responder.Error(err) - return - } - - if len(obj.Errors) > 0 { - code = http.StatusPartialContent - } - - logger.Debug("request resulted in valid object", "object", obj) - responder.Object(code, obj) + c.handleRequest(ctx, name, r, responder, logger) }), 30*time.Second), nil } -// listFolderFiles returns a list of files in a folder -func (c *filesConnector) listFolderFiles(ctx context.Context, filePath string, ref string, readWriter repository.ReaderWriter) (*provisioning.FileList, error) { - id, err := identity.GetRequester(ctx) +// handleRequest processes the HTTP request for files operations. +func (c *filesConnector) handleRequest(ctx context.Context, name string, r *http.Request, responder rest.Responder, logger logging.Logger) { + repo, err := c.getRepo(ctx, r.Method, name) if err != nil { - return nil, fmt.Errorf("missing auth info in context") + logger.Debug("failed to find repository", "error", err) + responder.Error(err) + return } - // TODO: replace with access check on the repo itself - if !id.GetOrgRole().Includes(identity.RoleAdmin) { - return nil, apierrors.NewForbidden(resources.DashboardResource.GroupResource(), "", - fmt.Errorf("requires admin role")) + readWriter, ok := repo.(repository.ReaderWriter) + if !ok { + responder.Error(apierrors.NewBadRequest("repository does not support read-writing")) + return } + dualReadWriter, err := c.createDualReadWriter(ctx, repo, readWriter) + if err != nil { + responder.Error(err) + return + } + + opts, err := c.parseRequestOptions(r, name, repo) + if err != nil { + responder.Error(apierrors.NewBadRequest(err.Error())) + return + } + + logger = logger.With("url", r.URL.Path, "ref", opts.Ref, "message", opts.Message) + ctx = logging.Context(r.Context(), logger) + + // Handle directory listing separately + isDir := safepath.IsDir(opts.Path) + if r.Method == http.MethodGet && isDir { + c.handleDirectoryListing(ctx, name, opts, readWriter, responder) + return + } + + if opts.Path == "" { + responder.Error(apierrors.NewBadRequest("missing request path")) + return + } + + obj, err := c.handleMethodRequest(ctx, r, opts, isDir, dualReadWriter) + if err != nil { + logger.Debug("got an error after processing request", "error", err) + respondWithError(responder, err) + return + } + + code := http.StatusOK + if len(obj.Errors) > 0 { + code = http.StatusPartialContent + } + + logger.Debug("request resulted in valid object", "object", obj) + responder.Object(code, obj) +} + +// createDualReadWriter sets up the dual read writer with all required dependencies. +func (c *filesConnector) createDualReadWriter(ctx context.Context, repo repository.Repository, readWriter repository.ReaderWriter) (*resources.DualReadWriter, error) { + parser, err := c.parsers.GetParser(ctx, readWriter) + if err != nil { + return nil, fmt.Errorf("failed to get parser: %w", err) + } + + clients, err := c.clients.Clients(ctx, repo.Config().Namespace) + if err != nil { + return nil, fmt.Errorf("failed to get clients: %w", err) + } + + folderClient, err := clients.Folder(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get folder client: %w", err) + } + + folders := resources.NewFolderManager(readWriter, folderClient, resources.NewEmptyFolderTree()) + return resources.NewDualReadWriter(readWriter, parser, folders, c.access), nil +} + +// parseRequestOptions extracts options from the HTTP request. +func (c *filesConnector) parseRequestOptions(r *http.Request, name string, repo repository.Repository) (resources.DualWriteOptions, error) { + query := r.URL.Query() + opts := resources.DualWriteOptions{ + Ref: query.Get("ref"), + Message: query.Get("message"), + SkipDryRun: query.Get("skipDryRun") == "true", + OriginalPath: query.Get("originalPath"), + Branch: repo.Config().Branch(), + } + + path, err := pathAfterPrefix(r.URL.Path, fmt.Sprintf("/%s/files", name)) + if err != nil { + return opts, err + } + opts.Path = path + + if err := resources.IsPathSupported(opts.Path); err != nil { + return opts, err + } + + return opts, nil +} + +// handleDirectoryListing handles GET requests for directory listing. +func (c *filesConnector) handleDirectoryListing(ctx context.Context, name string, opts resources.DualWriteOptions, readWriter repository.ReaderWriter, responder rest.Responder) { + if err := c.authorizeListFiles(ctx, name); err != nil { + responder.Error(err) + return + } + + files, err := c.listFolderFiles(ctx, opts.Path, opts.Ref, readWriter) + if err != nil { + responder.Error(err) + return + } + + responder.Object(http.StatusOK, files) +} + +// handleMethodRequest routes the request to the appropriate handler based on HTTP method. +func (c *filesConnector) handleMethodRequest(ctx context.Context, r *http.Request, opts resources.DualWriteOptions, isDir bool, dualReadWriter *resources.DualReadWriter) (*provisioning.ResourceWrapper, error) { + switch r.Method { + case http.MethodGet: + return c.handleGet(ctx, opts, dualReadWriter) + case http.MethodPost: + return c.handlePost(ctx, r, opts, isDir, dualReadWriter) + case http.MethodPut: + return c.handlePut(ctx, r, opts, isDir, dualReadWriter) + case http.MethodDelete: + return c.handleDelete(ctx, opts, dualReadWriter) + default: + return nil, apierrors.NewMethodNotSupported(provisioning.RepositoryResourceInfo.GroupResource(), r.Method) + } +} + +func (c *filesConnector) handleGet(ctx context.Context, opts resources.DualWriteOptions, dualReadWriter *resources.DualReadWriter) (*provisioning.ResourceWrapper, error) { + resource, err := dualReadWriter.Read(ctx, opts.Path, opts.Ref) + if err != nil { + return nil, err + } + return resource.AsResourceWrapper(), nil +} + +func (c *filesConnector) handlePost(ctx context.Context, r *http.Request, opts resources.DualWriteOptions, isDir bool, dualReadWriter *resources.DualReadWriter) (*provisioning.ResourceWrapper, error) { + // Check if this is a move operation (originalPath query parameter is present) + if opts.OriginalPath != "" { + return c.handleMove(ctx, r, opts, isDir, dualReadWriter) + } + + if isDir { + return dualReadWriter.CreateFolder(ctx, opts) + } + + data, err := readBody(r, filesMaxBodySize) + if err != nil { + return nil, err + } + opts.Data = data + + resource, err := dualReadWriter.CreateResource(ctx, opts) + if err != nil { + return nil, err + } + return resource.AsResourceWrapper(), nil +} + +func (c *filesConnector) handleMove(ctx context.Context, r *http.Request, opts resources.DualWriteOptions, isDir bool, dualReadWriter *resources.DualReadWriter) (*provisioning.ResourceWrapper, error) { + // For move operations, only read body for file moves (not directory moves) + if !isDir { + data, err := readBody(r, filesMaxBodySize) + if err != nil { + return nil, err + } + opts.Data = data + } + + resource, err := dualReadWriter.MoveResource(ctx, opts) + if err != nil { + return nil, err + } + return resource.AsResourceWrapper(), nil +} + +func (c *filesConnector) handlePut(ctx context.Context, r *http.Request, opts resources.DualWriteOptions, isDir bool, dualReadWriter *resources.DualReadWriter) (*provisioning.ResourceWrapper, error) { + if isDir { + return nil, apierrors.NewMethodNotSupported(provisioning.RepositoryResourceInfo.GroupResource(), r.Method) + } + + data, err := readBody(r, filesMaxBodySize) + if err != nil { + return nil, err + } + opts.Data = data + + resource, err := dualReadWriter.UpdateResource(ctx, opts) + if err != nil { + return nil, err + } + return resource.AsResourceWrapper(), nil +} + +func (c *filesConnector) handleDelete(ctx context.Context, opts resources.DualWriteOptions, dualReadWriter *resources.DualReadWriter) (*provisioning.ResourceWrapper, error) { + resource, err := dualReadWriter.Delete(ctx, opts) + if err != nil { + return nil, err + } + return resource.AsResourceWrapper(), nil +} + +// authorizeListFiles checks if the user has repositories:read permission for listing files. +// The access checker handles AccessPolicy identities, namespace resolution, and role-based fallback internally. +func (c *filesConnector) authorizeListFiles(ctx context.Context, repoName string) error { + return c.access.Check(ctx, authlib.CheckRequest{ + Verb: utils.VerbGet, + Group: provisioning.GROUP, + Resource: provisioning.RepositoryResourceInfo.GetName(), + Name: repoName, + }, "") +} + +// listFolderFiles returns a list of files in a folder. +// Authorization is checked via authorizeListFiles before calling this function. +func (c *filesConnector) listFolderFiles(ctx context.Context, filePath string, ref string, readWriter repository.ReaderWriter) (*provisioning.FileList, error) { // TODO: Implement folder navigation if len(filePath) > 0 { return nil, apierrors.NewBadRequest("folder navigation not yet supported") diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index f5376cb20ab..26e97f56b3c 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -29,6 +29,7 @@ import ( "github.com/grafana/grafana-app-sdk/logging" provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + "github.com/grafana/grafana/apps/provisioning/pkg/auth" connectionvalidation "github.com/grafana/grafana/apps/provisioning/pkg/connection" appcontroller "github.com/grafana/grafana/apps/provisioning/pkg/controller" clientset "github.com/grafana/grafana/apps/provisioning/pkg/generated/clientset/versioned" @@ -111,7 +112,10 @@ type APIBuilder struct { unified resource.ResourceClient repoFactory repository.Factory client client.ProvisioningV0alpha1Interface - access authlib.AccessChecker + access auth.AccessChecker + accessWithAdmin auth.AccessChecker + accessWithEditor auth.AccessChecker + accessWithViewer auth.AccessChecker statusPatcher *appcontroller.RepositoryStatusPatcher healthChecker *controller.HealthChecker validator repository.RepositoryValidator @@ -158,6 +162,14 @@ func NewAPIBuilder( parsers := resources.NewParserFactory(clients) resourceLister := resources.NewResourceListerForMigrations(unified) + // Create access checker based on mode + var accessChecker auth.AccessChecker + if useExclusivelyAccessCheckerForAuthz { + accessChecker = auth.NewTokenAccessChecker(access) + } else { + accessChecker = auth.NewSessionAccessChecker(access) + } + b := &APIBuilder{ onlyApiServer: onlyApiServer, tracer: tracer, @@ -170,7 +182,10 @@ func NewAPIBuilder( resourceLister: resourceLister, dashboardAccess: dashboardAccess, unified: unified, - access: access, + access: accessChecker, + accessWithAdmin: accessChecker.WithFallbackRole(identity.RoleAdmin), + accessWithEditor: accessChecker.WithFallbackRole(identity.RoleEditor), + accessWithViewer: accessChecker.WithFallbackRole(identity.RoleViewer), jobHistoryConfig: jobHistoryConfig, extraWorkers: extraWorkers, restConfigGetter: restConfigGetter, @@ -298,161 +313,142 @@ func (b *APIBuilder) GetAuthorizer() authorizer.Authorizer { } } - info, ok := authlib.AuthInfoFrom(ctx) - // when running as standalone API server, the identity type may not always match TypeAccessPolicy - // so we allow it to use the access checker if there is any auth info available - if ok && (authlib.IsIdentityType(info.GetIdentityType(), authlib.TypeAccessPolicy) || b.useExclusivelyAccessCheckerForAuthz) { - res, err := b.access.Check(ctx, info, authlib.CheckRequest{ - Verb: a.GetVerb(), - Group: a.GetAPIGroup(), - Resource: a.GetResource(), - Name: a.GetName(), - Namespace: a.GetNamespace(), - Subresource: a.GetSubresource(), - Path: a.GetPath(), - }, "") - if err != nil { - return authorizer.DecisionDeny, "failed to perform authorization", err - } - - if !res.Allowed { - return authorizer.DecisionDeny, "permission denied", nil - } - - return authorizer.DecisionAllow, "", nil - } - - id, err := identity.GetRequester(ctx) - if err != nil { - return authorizer.DecisionDeny, "failed to find requester", err - } - - return b.authorizeResource(ctx, a, id) + return b.authorizeResource(ctx, a) }) } // authorizeResource handles authorization for different resources. -// Different routes may need different permissions. -// * Reading and modifying a repository's configuration requires administrator privileges. -// * Reading a repository's limited configuration (/stats & /settings) requires viewer privileges. -// * Reading a repository's files requires viewer privileges. -// * Reading a repository's refs requires viewer privileges. -// * Editing a repository's files requires editor privileges. -// * Syncing a repository requires editor privileges. -// * Exporting a repository requires administrator privileges. -// * Migrating a repository requires administrator privileges. -// * Testing a repository configuration requires administrator privileges. -// * Viewing a repository's history requires editor privileges. -func (b *APIBuilder) authorizeResource(ctx context.Context, a authorizer.Attributes, id identity.Requester) (authorizer.Decision, string, error) { +// Uses fine-grained permissions defined in accesscontrol.go: +// +// Repositories: +// - CRUD: repositories:create/read/write/delete +// - Subresources: files (any auth), refs (editor), resources/history/status (admin) +// - Test: repositories:write +// - Jobs subresource: jobs:create/read +// +// Connections: +// - CRUD: connections:create/read/write/delete +// - Status: connections:read +// +// Jobs: +// - CRUD: jobs:create/read/write/delete +// +// Historic Jobs: +// - Read-only: historicjobs:read +// +// Settings: +// - settings:read - granted to Viewer (all logged-in users) +// +// Stats: +// - stats:read - granted to Admin only +func (b *APIBuilder) authorizeResource(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { switch a.GetResource() { case provisioning.RepositoryResourceInfo.GetName(): - return b.authorizeRepositorySubresource(a, id) - case "stats": - return b.authorizeStats(id) - case "settings": - return b.authorizeSettings(id) - case provisioning.JobResourceInfo.GetName(), provisioning.HistoricJobResourceInfo.GetName(): - return b.authorizeJobs(id) + return b.authorizeRepositorySubresource(ctx, a) case provisioning.ConnectionResourceInfo.GetName(): - return b.authorizeConnectionSubresource(a, id) + return b.authorizeConnectionSubresource(ctx, a) + case provisioning.JobResourceInfo.GetName(): + return toAuthorizerDecision(b.accessWithEditor.Check(ctx, authlib.CheckRequest{ + Verb: a.GetVerb(), + Group: provisioning.GROUP, + Resource: provisioning.JobResourceInfo.GetName(), + Name: a.GetName(), + Namespace: a.GetNamespace(), + }, "")) + case provisioning.HistoricJobResourceInfo.GetName(): + // Historic jobs are read-only and admin-only (not editor) + return toAuthorizerDecision(b.accessWithAdmin.Check(ctx, authlib.CheckRequest{ + Verb: a.GetVerb(), + Group: provisioning.GROUP, + Resource: provisioning.HistoricJobResourceInfo.GetName(), + Name: a.GetName(), + Namespace: a.GetNamespace(), + }, "")) + case "settings": + // Settings are read-only and accessible by all logged-in users (Viewer role) + return toAuthorizerDecision(b.accessWithViewer.Check(ctx, authlib.CheckRequest{ + Verb: a.GetVerb(), + Group: provisioning.GROUP, + Resource: "settings", + Namespace: a.GetNamespace(), + }, "")) + case "stats": + // Stats are read-only and admin-only + return toAuthorizerDecision(b.accessWithAdmin.Check(ctx, authlib.CheckRequest{ + Verb: a.GetVerb(), + Group: provisioning.GROUP, + Resource: "stats", + Namespace: a.GetNamespace(), + }, "")) default: - return b.authorizeDefault(id) + return b.authorizeDefault(ctx) } } // authorizeRepositorySubresource handles authorization for repository subresources. -func (b *APIBuilder) authorizeRepositorySubresource(a authorizer.Attributes, id identity.Requester) (authorizer.Decision, string, error) { - // TODO: Support more fine-grained permissions than the basic roles. Especially on Enterprise. - switch a.GetSubresource() { - case "", "test": - // Doing something with the repository itself. - if id.GetOrgRole().Includes(identity.RoleAdmin) { - return authorizer.DecisionAllow, "", nil - } - return authorizer.DecisionDeny, "admin role is required", nil - - case "jobs": - // Posting jobs requires editor privileges (for syncing). - if id.GetOrgRole().Includes(identity.RoleAdmin) || id.GetOrgRole().Includes(identity.RoleEditor) { - return authorizer.DecisionAllow, "", nil - } - return authorizer.DecisionDeny, "editor role is required", nil - - case "refs": - // This is strictly a read operation. It is handy on the frontend for viewers. - if id.GetOrgRole().Includes(identity.RoleViewer) { - return authorizer.DecisionAllow, "", nil - } - return authorizer.DecisionDeny, "viewer role is required", nil - - case "files": - // Access to files is controlled by the AccessClient - return authorizer.DecisionAllow, "", nil - - case "resources", "sync", "history": - // These are strictly read operations. - // Sync can also be somewhat destructive, but it's expected to be fine to import changes. - if id.GetOrgRole().Includes(identity.RoleEditor) { - return authorizer.DecisionAllow, "", nil - } - return authorizer.DecisionDeny, "editor role is required", nil - - case "status": - if id.GetOrgRole().Includes(identity.RoleViewer) && a.GetVerb() == apiutils.VerbGet { - return authorizer.DecisionAllow, "", nil - } - return authorizer.DecisionDeny, "users cannot update the status of a repository", nil - - default: - if id.GetIsGrafanaAdmin() { - return authorizer.DecisionAllow, "", nil - } - return authorizer.DecisionDeny, "unmapped subresource defaults to no access", nil - } -} - -// authorizeStats handles authorization for stats resource. -func (b *APIBuilder) authorizeStats(id identity.Requester) (authorizer.Decision, string, error) { - // This can leak information one shouldn't necessarily have access to. - if id.GetOrgRole().Includes(identity.RoleAdmin) { - return authorizer.DecisionAllow, "", nil - } - return authorizer.DecisionDeny, "admin role is required", nil -} - -// authorizeSettings handles authorization for settings resource. -func (b *APIBuilder) authorizeSettings(id identity.Requester) (authorizer.Decision, string, error) { - // This is strictly a read operation. It is handy on the frontend for viewers. - if id.GetOrgRole().Includes(identity.RoleViewer) { - return authorizer.DecisionAllow, "", nil - } - return authorizer.DecisionDeny, "viewer role is required", nil -} - -// authorizeJobs handles authorization for job resources. -func (b *APIBuilder) authorizeJobs(id identity.Requester) (authorizer.Decision, string, error) { - // Jobs are shown on the configuration page. - if id.GetOrgRole().Includes(identity.RoleAdmin) { - return authorizer.DecisionAllow, "", nil - } - return authorizer.DecisionDeny, "admin role is required", nil -} - -// authorizeRepositorySubresource handles authorization for connections subresources. -func (b *APIBuilder) authorizeConnectionSubresource(a authorizer.Attributes, id identity.Requester) (authorizer.Decision, string, error) { +// Uses the access checker with verb-based authorization. +func (b *APIBuilder) authorizeRepositorySubresource(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { switch a.GetSubresource() { + // Repository CRUD - use access checker with the actual verb case "": - // Doing something with the connection itself. - if id.GetOrgRole().Includes(identity.RoleAdmin) { - return authorizer.DecisionAllow, "", nil - } - return authorizer.DecisionDeny, "admin role is required", nil - case "status": - if id.GetOrgRole().Includes(identity.RoleViewer) && a.GetVerb() == apiutils.VerbGet { - return authorizer.DecisionAllow, "", nil - } - return authorizer.DecisionDeny, "users cannot update the status of a connection", nil + return toAuthorizerDecision(b.accessWithAdmin.Check(ctx, authlib.CheckRequest{ + Verb: a.GetVerb(), + Group: provisioning.GROUP, + Resource: provisioning.RepositoryResourceInfo.GetName(), + Name: a.GetName(), + Namespace: a.GetNamespace(), + }, "")) + + // Test requires write permission (testing before save) + case "test": + return toAuthorizerDecision(b.accessWithAdmin.Check(ctx, authlib.CheckRequest{ + Verb: apiutils.VerbUpdate, + Group: provisioning.GROUP, + Resource: provisioning.RepositoryResourceInfo.GetName(), + Name: a.GetName(), + Namespace: a.GetNamespace(), + }, "")) + + // Files subresource: allow any authenticated user at route level. + // Directory listing checks repositories:read in the connector. + // Individual file operations are authorized by DualReadWriter based on the actual resource. + case "files": + return authorizer.DecisionAllow, "", nil + + // refs subresource - editors need to see branches to push changes + case "refs": + return toAuthorizerDecision(b.accessWithEditor.Check(ctx, authlib.CheckRequest{ + Verb: apiutils.VerbGet, + Group: provisioning.GROUP, + Resource: provisioning.RepositoryResourceInfo.GetName(), + Name: a.GetName(), + Namespace: a.GetNamespace(), + }, "")) + + // Read-only subresources: resources, history, status (admin only) + case "resources", "history", "status": + return toAuthorizerDecision(b.accessWithAdmin.Check(ctx, authlib.CheckRequest{ + Verb: apiutils.VerbGet, + Group: provisioning.GROUP, + Resource: provisioning.RepositoryResourceInfo.GetName(), + Name: a.GetName(), + Namespace: a.GetNamespace(), + }, "")) + + // Jobs subresource - check jobs permissions with the verb (editors can manage jobs) + case "jobs": + return toAuthorizerDecision(b.accessWithEditor.Check(ctx, authlib.CheckRequest{ + Verb: a.GetVerb(), + Group: provisioning.GROUP, + Resource: provisioning.JobResourceInfo.GetName(), + Namespace: a.GetNamespace(), + }, "")) + default: + id, err := identity.GetRequester(ctx) + if err != nil { + return authorizer.DecisionDeny, "failed to find requester", err + } if id.GetIsGrafanaAdmin() { return authorizer.DecisionAllow, "", nil } @@ -460,8 +456,60 @@ func (b *APIBuilder) authorizeConnectionSubresource(a authorizer.Attributes, id } } +// authorizeConnectionSubresource handles authorization for connection subresources. +// Uses the access checker with verb-based authorization. +func (b *APIBuilder) authorizeConnectionSubresource(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { + switch a.GetSubresource() { + // Connection CRUD - use access checker with the actual verb + case "": + return toAuthorizerDecision(b.accessWithAdmin.Check(ctx, authlib.CheckRequest{ + Verb: a.GetVerb(), + Group: provisioning.GROUP, + Resource: provisioning.ConnectionResourceInfo.GetName(), + Name: a.GetName(), + Namespace: a.GetNamespace(), + }, "")) + + // Status is read-only + case "status": + return toAuthorizerDecision(b.accessWithAdmin.Check(ctx, authlib.CheckRequest{ + Verb: apiutils.VerbGet, + Group: provisioning.GROUP, + Resource: provisioning.ConnectionResourceInfo.GetName(), + Name: a.GetName(), + Namespace: a.GetNamespace(), + }, "")) + + default: + id, err := identity.GetRequester(ctx) + if err != nil { + return authorizer.DecisionDeny, "failed to find requester", err + } + if id.GetIsGrafanaAdmin() { + return authorizer.DecisionAllow, "", nil + } + return authorizer.DecisionDeny, "unmapped subresource defaults to no access", nil + } +} + +// ---------------------------------------------------------------------------- +// Authorization helpers +// ---------------------------------------------------------------------------- + +// toAuthorizerDecision converts an access check error to an authorizer decision tuple. +func toAuthorizerDecision(err error) (authorizer.Decision, string, error) { + if err != nil { + return authorizer.DecisionDeny, err.Error(), nil + } + return authorizer.DecisionAllow, "", nil +} + // authorizeDefault handles authorization for unmapped resources. -func (b *APIBuilder) authorizeDefault(id identity.Requester) (authorizer.Decision, string, error) { +func (b *APIBuilder) authorizeDefault(ctx context.Context) (authorizer.Decision, string, error) { + id, err := identity.GetRequester(ctx) + if err != nil { + return authorizer.DecisionDeny, "failed to find requester", err + } // We haven't bothered with this kind yet. if id.GetIsGrafanaAdmin() { return authorizer.DecisionAllow, "", nil @@ -558,7 +606,7 @@ func (b *APIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupI // TODO: Add some logic so that the connectors can registered themselves and we don't have logic all over the place storage[provisioning.RepositoryResourceInfo.StoragePath("test")] = NewTestConnector(b, repository.NewRepositoryTesterWithExistingChecker(repository.NewSimpleRepositoryTester(b.validator), b.VerifyAgainstExistingRepositories)) - storage[provisioning.RepositoryResourceInfo.StoragePath("files")] = NewFilesConnector(b, b.parsers, b.clients, b.access) + storage[provisioning.RepositoryResourceInfo.StoragePath("files")] = NewFilesConnector(b, b.parsers, b.clients, b.accessWithAdmin) storage[provisioning.RepositoryResourceInfo.StoragePath("refs")] = NewRefsConnector(b) storage[provisioning.RepositoryResourceInfo.StoragePath("resources")] = &listConnector{ getter: b, diff --git a/pkg/registry/apis/provisioning/resources/dualwriter.go b/pkg/registry/apis/provisioning/resources/dualwriter.go index 9180ace494d..e8a7ee83dd0 100644 --- a/pkg/registry/apis/provisioning/resources/dualwriter.go +++ b/pkg/registry/apis/provisioning/resources/dualwriter.go @@ -12,6 +12,7 @@ import ( authlib "github.com/grafana/authlib/types" "github.com/grafana/grafana-app-sdk/logging" provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + "github.com/grafana/grafana/apps/provisioning/pkg/auth" "github.com/grafana/grafana/apps/provisioning/pkg/repository" "github.com/grafana/grafana/apps/provisioning/pkg/safepath" "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" @@ -32,7 +33,7 @@ type DualReadWriter struct { repo repository.ReaderWriter parser Parser folders *FolderManager - access authlib.AccessChecker + access auth.AccessChecker } type DualWriteOptions struct { @@ -48,7 +49,7 @@ type DualWriteOptions struct { Branch string // Configured default branch } -func NewDualReadWriter(repo repository.ReaderWriter, parser Parser, folders *FolderManager, access authlib.AccessChecker) *DualReadWriter { +func NewDualReadWriter(repo repository.ReaderWriter, parser Parser, folders *FolderManager, access auth.AccessChecker) *DualReadWriter { return &DualReadWriter{repo: repo, parser: parser, folders: folders, access: access} } @@ -492,11 +493,6 @@ func (r *DualReadWriter) moveFile(ctx context.Context, opts DualWriteOptions) (* } func (r *DualReadWriter) authorize(ctx context.Context, parsed *ParsedResource, verb string) error { - id, err := identity.GetRequester(ctx) - if err != nil { - return apierrors.NewUnauthorized(err.Error()) - } - var name string if parsed.Existing != nil { name = parsed.Existing.GetName() @@ -504,27 +500,15 @@ func (r *DualReadWriter) authorize(ctx context.Context, parsed *ParsedResource, name = parsed.Obj.GetName() } - rsp, err := r.access.Check(ctx, id, authlib.CheckRequest{ - Group: parsed.GVR.Group, - Resource: parsed.GVR.Resource, - Namespace: id.GetNamespace(), - Name: name, - Verb: verb, + return r.access.Check(ctx, authlib.CheckRequest{ + Group: parsed.GVR.Group, + Resource: parsed.GVR.Resource, + Name: name, + Verb: verb, }, parsed.Meta.GetFolder()) - if err != nil || !rsp.Allowed { - return apierrors.NewForbidden(parsed.GVR.GroupResource(), parsed.Obj.GetName(), - fmt.Errorf("no access to perform %s on the resource", verb)) - } - - return nil } func (r *DualReadWriter) authorizeCreateFolder(ctx context.Context, path string) error { - id, err := identity.GetRequester(ctx) - if err != nil { - return apierrors.NewUnauthorized(err.Error()) - } - // Determine parent folder from path parentFolder := "" if path != "" { @@ -537,19 +521,12 @@ func (r *DualReadWriter) authorizeCreateFolder(ctx context.Context, path string) } // For folder create operations, use empty name to check parent folder permissions - rsp, err := r.access.Check(ctx, id, authlib.CheckRequest{ - Group: FolderResource.Group, - Resource: FolderResource.Resource, - Namespace: id.GetNamespace(), - Name: "", // Empty name for create operations - Verb: utils.VerbCreate, + return r.access.Check(ctx, authlib.CheckRequest{ + Group: FolderResource.Group, + Resource: FolderResource.Resource, + Name: "", // Empty name for create operations + Verb: utils.VerbCreate, }, parentFolder) - if err != nil || !rsp.Allowed { - return apierrors.NewForbidden(FolderResource.GroupResource(), path, - fmt.Errorf("no access to create folder in parent folder '%s'", parentFolder)) - } - - return nil } func (r *DualReadWriter) deleteFolder(ctx context.Context, opts DualWriteOptions) (*ParsedResource, error) { diff --git a/pkg/services/authz/rbac/mapper.go b/pkg/services/authz/rbac/mapper.go index 9444d35d0ae..dcf2432fb2c 100644 --- a/pkg/services/authz/rbac/mapper.go +++ b/pkg/services/authz/rbac/mapper.go @@ -279,8 +279,11 @@ func NewMapperRegistry() MapperRegistry { }, "provisioning.grafana.app": { "repositories": newResourceTranslation("provisioning.repositories", "uid", false, skipScopeOnAllVerbs), + "connections": newResourceTranslation("provisioning.connections", "uid", false, skipScopeOnAllVerbs), "jobs": newResourceTranslation("provisioning.jobs", "uid", false, skipScopeOnAllVerbs), "historicjobs": newResourceTranslation("provisioning.historicjobs", "uid", false, skipScopeOnAllVerbs), + "settings": newResourceTranslation("provisioning.settings", "", false, skipScopeOnAllVerbs), + "stats": newResourceTranslation("provisioning.stats", "", false, skipScopeOnAllVerbs), }, "secret.grafana.app": { "securevalues": newResourceTranslation("secret.securevalues", "uid", false, nil), diff --git a/pkg/tests/apis/provisioning/connection_status_auth_test.go b/pkg/tests/apis/provisioning/connection_status_auth_test.go new file mode 100644 index 00000000000..fbddd85999a --- /dev/null +++ b/pkg/tests/apis/provisioning/connection_status_auth_test.go @@ -0,0 +1,88 @@ +package provisioning + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/grafana/grafana/pkg/util/testutil" +) + +func TestIntegrationProvisioning_ConnectionStatusAuthorization(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + helper := runGrafana(t) + ctx := context.Background() + createOptions := metav1.CreateOptions{FieldValidation: "Strict"} + + // Create a connection for testing + connection := &unstructured.Unstructured{Object: map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Connection", + "metadata": map[string]any{ + "name": "connection-status-test", + "namespace": "default", + }, + "spec": map[string]any{ + "type": "github", + "github": map[string]any{ + "appID": "123456", + "installationID": "454545", + }, + }, + "secure": map[string]any{ + "privateKey": map[string]any{ + "create": "someSecret", + }, + }, + }} + + _, err := helper.Connections.Resource.Create(ctx, connection, createOptions) + require.NoError(t, err, "failed to create connection") + + t.Run("admin can GET connection status", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Get(). + Namespace("default"). + Resource("connections"). + Name("connection-status-test"). + SubResource("status"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "admin should be able to GET connection status") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) + + t.Run("editor cannot GET connection status", func(t *testing.T) { + var statusCode int + result := helper.EditorREST.Get(). + Namespace("default"). + Resource("connections"). + Name("connection-status-test"). + SubResource("status"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "editor should not be able to GET connection status") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + + t.Run("viewer cannot GET connection status", func(t *testing.T) { + var statusCode int + result := helper.ViewerREST.Get(). + Namespace("default"). + Resource("connections"). + Name("connection-status-test"). + SubResource("status"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "viewer should not be able to GET connection status") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) +} diff --git a/pkg/tests/apis/provisioning/historicjobs_auth_test.go b/pkg/tests/apis/provisioning/historicjobs_auth_test.go new file mode 100644 index 00000000000..fc11f8d00b3 --- /dev/null +++ b/pkg/tests/apis/provisioning/historicjobs_auth_test.go @@ -0,0 +1,95 @@ +package provisioning + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" + "github.com/grafana/grafana/pkg/util/testutil" +) + +func TestIntegrationProvisioning_HistoricJobsAuthorization(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + helper := runGrafana(t) + ctx := context.Background() + + const repo = "historicjobs-auth-test" + testRepo := TestRepo{ + Name: repo, + Target: "folder", + Copies: map[string]string{}, // No files needed for this test + ExpectedDashboards: 0, + ExpectedFolders: 1, // Repository creates a folder + } + helper.CreateRepo(t, testRepo) + + // Trigger a job to create a historic job entry + jobSpec := provisioning.JobSpec{ + Action: provisioning.JobActionPull, + Pull: &provisioning.SyncJobOptions{}, + } + body := asJSON(jobSpec) + + // Create a job as admin + var statusCode int + result := helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("jobs"). + Body(body). + SetHeader("Content-Type", "application/json"). + Do(ctx).StatusCode(&statusCode) + require.NoError(t, result.Error(), "should be able to create job") + require.Equal(t, http.StatusAccepted, statusCode) + + // Wait for job to complete and become historic + helper.AwaitJobs(t, repo) + historicJob := helper.AwaitLatestHistoricJob(t, repo) + require.NotNil(t, historicJob, "should have a historic job") + + historicJobName := historicJob.GetName() + + t.Run("admin can GET historic job", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Get(). + Namespace("default"). + Resource("historicjobs"). + Name(historicJobName). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "admin should be able to GET historic job") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) + + t.Run("editor cannot GET historic job", func(t *testing.T) { + var statusCode int + result := helper.EditorREST.Get(). + Namespace("default"). + Resource("historicjobs"). + Name(historicJobName). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "editor should not be able to GET historic job") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + + t.Run("viewer cannot GET historic job", func(t *testing.T) { + var statusCode int + result := helper.ViewerREST.Get(). + Namespace("default"). + Resource("historicjobs"). + Name(historicJobName). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "viewer should not be able to GET historic job") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) +} diff --git a/pkg/tests/apis/provisioning/repository_subresources_auth_test.go b/pkg/tests/apis/provisioning/repository_subresources_auth_test.go new file mode 100644 index 00000000000..54a2f173b51 --- /dev/null +++ b/pkg/tests/apis/provisioning/repository_subresources_auth_test.go @@ -0,0 +1,236 @@ +package provisioning + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + + "github.com/grafana/grafana/pkg/util/testutil" +) + +func TestIntegrationProvisioning_RepositorySubresourcesAuthorization(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + helper := runGrafana(t) + ctx := context.Background() + + const repo = "subresources-auth-test" + testRepo := TestRepo{ + Name: repo, + Target: "folder", + Copies: map[string]string{}, // No files needed for this test + ExpectedDashboards: 0, + ExpectedFolders: 1, // Repository creates a folder + } + helper.CreateRepo(t, testRepo) + + t.Run("test subresource", func(t *testing.T) { + newRepoConfig := map[string]any{ + "apiVersion": "provisioning.grafana.app/v0alpha1", + "kind": "Repository", + "spec": map[string]any{ + "title": "Test Configuration", + "type": "local", + "local": map[string]any{ + "path": helper.ProvisioningPath, + }, + "workflows": []string{"write"}, + "sync": map[string]any{ + "enabled": true, + "target": "folder", + "intervalSeconds": 10, + }, + }, + } + configBytes, err := json.Marshal(newRepoConfig) + require.NoError(t, err) + + t.Run("admin can POST test", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Post(). + Namespace("default"). + Resource("repositories"). + Name("test-config-auth"). + SubResource("test"). + Body(configBytes). + SetHeader("Content-Type", "application/json"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "admin should be able to POST test") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) + + t.Run("editor cannot POST test", func(t *testing.T) { + var statusCode int + result := helper.EditorREST.Post(). + Namespace("default"). + Resource("repositories"). + Name("test-config-auth"). + SubResource("test"). + Body(configBytes). + SetHeader("Content-Type", "application/json"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "editor should not be able to POST test") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + + t.Run("viewer cannot POST test", func(t *testing.T) { + var statusCode int + result := helper.ViewerREST.Post(). + Namespace("default"). + Resource("repositories"). + Name("test-config-auth"). + SubResource("test"). + Body(configBytes). + SetHeader("Content-Type", "application/json"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "viewer should not be able to POST test") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + }) + + t.Run("resources subresource", func(t *testing.T) { + t.Run("admin can GET resources", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("resources"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "admin should be able to GET resources") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) + + t.Run("editor cannot GET resources", func(t *testing.T) { + var statusCode int + result := helper.EditorREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("resources"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "editor should not be able to GET resources") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + + t.Run("viewer cannot GET resources", func(t *testing.T) { + var statusCode int + result := helper.ViewerREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("resources"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "viewer should not be able to GET resources") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + }) + + t.Run("history subresource", func(t *testing.T) { + t.Run("admin can GET history (or BadRequest if not supported)", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("history"). + Do(ctx).StatusCode(&statusCode) + + // Admin should pass authorization - may get BadRequest if repo doesn't support history + // but should NOT get Forbidden (which would indicate authorization failure) + if result.Error() != nil { + require.False(t, apierrors.IsForbidden(result.Error()), "admin should not get Forbidden error") + // Local repos don't support history, so BadRequest is expected + require.True(t, apierrors.IsBadRequest(result.Error()) || statusCode == http.StatusBadRequest, + "should get BadRequest if history not supported, not Forbidden") + } else { + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK if history is supported") + } + }) + + t.Run("editor cannot GET history", func(t *testing.T) { + var statusCode int + result := helper.EditorREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("history"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "editor should not be able to GET history") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + + t.Run("viewer cannot GET history", func(t *testing.T) { + var statusCode int + result := helper.ViewerREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("history"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "viewer should not be able to GET history") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + }) + + t.Run("status subresource", func(t *testing.T) { + t.Run("admin can GET status", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("status"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "admin should be able to GET status") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) + + t.Run("editor cannot GET status", func(t *testing.T) { + var statusCode int + result := helper.EditorREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("status"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "editor should not be able to GET status") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + + t.Run("viewer cannot GET status", func(t *testing.T) { + var statusCode int + result := helper.ViewerREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("status"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "viewer should not be able to GET status") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + }) +} diff --git a/pkg/tests/apis/provisioning/repository_test.go b/pkg/tests/apis/provisioning/repository_test.go index 3cee3cf4aba..f2447e71d23 100644 --- a/pkg/tests/apis/provisioning/repository_test.go +++ b/pkg/tests/apis/provisioning/repository_test.go @@ -956,3 +956,66 @@ func TestIntegrationProvisioning_JobPermissions(t *testing.T) { require.Equal(t, http.StatusAccepted, statusCode, "should return 202 Accepted") }) } + +func TestIntegrationProvisioning_RefsPermissions(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + helper := runGrafana(t) + ctx := context.Background() + + const repo = "refs-permissions-test" + testRepo := TestRepo{ + Name: repo, + Template: "testdata/github-readonly.json.tmpl", + Target: "folder", + ExpectedDashboards: 3, + ExpectedFolders: 3, // Repository creates folders + } + helper.CreateRepo(t, testRepo) + + t.Run("editor can GET refs", func(t *testing.T) { + var statusCode int + result := helper.EditorREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("refs"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "editor should be able to GET refs") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + + // Verify we can parse the refs and it contains at least main branch + refs := &provisioning.RefList{} + err := result.Into(refs) + require.NoError(t, err, "should parse refs response") + require.NotEmpty(t, refs.Items, "should have at least one ref") + }) + + t.Run("viewer cannot GET refs", func(t *testing.T) { + var statusCode int + result := helper.ViewerREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("refs"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "viewer should not be able to GET refs") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + + t.Run("admin can GET refs", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Get(). + Namespace("default"). + Resource("repositories"). + Name(repo). + SubResource("refs"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "admin should be able to GET refs") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) +} diff --git a/pkg/tests/apis/provisioning/settings_stats_auth_test.go b/pkg/tests/apis/provisioning/settings_stats_auth_test.go new file mode 100644 index 00000000000..d438066235a --- /dev/null +++ b/pkg/tests/apis/provisioning/settings_stats_auth_test.go @@ -0,0 +1,104 @@ +package provisioning + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + + "github.com/grafana/grafana/pkg/util/testutil" +) + +func TestIntegrationProvisioning_SettingsAuthorization(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + helper := runGrafana(t) + ctx := context.Background() + + t.Run("viewer can GET settings", func(t *testing.T) { + var statusCode int + result := helper.ViewerREST.Get(). + Namespace("default"). + Resource("settings"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "viewer should be able to GET settings") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) + + t.Run("editor can GET settings", func(t *testing.T) { + var statusCode int + result := helper.EditorREST.Get(). + Namespace("default"). + Resource("settings"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "editor should be able to GET settings") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) + + t.Run("admin can GET settings", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Get(). + Namespace("default"). + Resource("settings"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "admin should be able to GET settings") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) +} + +func TestIntegrationProvisioning_StatsAuthorization(t *testing.T) { + testutil.SkipIntegrationTestInShortMode(t) + + helper := runGrafana(t) + ctx := context.Background() + + // Create a repository to ensure stats endpoint has data + const repo = "stats-auth-test" + helper.CreateRepo(t, TestRepo{ + Name: repo, + Target: "folder", + Copies: map[string]string{}, + ExpectedDashboards: 0, + ExpectedFolders: 1, + }) + + t.Run("admin can GET stats", func(t *testing.T) { + var statusCode int + result := helper.AdminREST.Get(). + Namespace("default"). + Resource("stats"). + Do(ctx).StatusCode(&statusCode) + + require.NoError(t, result.Error(), "admin should be able to GET stats") + require.Equal(t, http.StatusOK, statusCode, "should return 200 OK") + }) + + t.Run("editor cannot GET stats", func(t *testing.T) { + var statusCode int + result := helper.EditorREST.Get(). + Namespace("default"). + Resource("stats"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "editor should not be able to GET stats") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) + + t.Run("viewer cannot GET stats", func(t *testing.T) { + var statusCode int + result := helper.ViewerREST.Get(). + Namespace("default"). + Resource("stats"). + Do(ctx).StatusCode(&statusCode) + + require.Error(t, result.Error(), "viewer should not be able to GET stats") + require.Equal(t, http.StatusForbidden, statusCode, "should return 403 Forbidden") + require.True(t, apierrors.IsForbidden(result.Error()), "error should be forbidden") + }) +} From 19c9f21cc4ca23f75851eaaa7ef38d42c3b641aa Mon Sep 17 00:00:00 2001 From: Anna Urbiztondo Date: Fri, 19 Dec 2025 15:13:35 +0100 Subject: [PATCH 254/297] Docs: Corrections for full instance sync (#115615) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Corrections for full instance sync * Edits * Feedback * Migration checkbox * Edit * Update docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md Co-authored-by: Roberto Jiménez Sánchez * Mention to export * Prettier --------- Co-authored-by: Roberto Jiménez Sánchez --- .../provision-resources/file-path-setup.md | 23 ++++++++----- .../provision-resources/git-sync-setup.md | 32 +++++++++++++++---- .../provision-resources/intro-git-sync.md | 4 +-- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/docs/sources/as-code/observability-as-code/provision-resources/file-path-setup.md b/docs/sources/as-code/observability-as-code/provision-resources/file-path-setup.md index d071ccca0de..4c0d22c3be9 100644 --- a/docs/sources/as-code/observability-as-code/provision-resources/file-path-setup.md +++ b/docs/sources/as-code/observability-as-code/provision-resources/file-path-setup.md @@ -128,25 +128,32 @@ The set up process verifies the path and provides an error message if a problem #### Synchronization limitations -Full instance sync is not available in Grafana Cloud. +{{< admonition type="caution" >}} -In Grafana OSS/Enterprise: +Full instance sync is not available in Grafana Cloud and is experimental and unsupported in Grafana OSS/Enterprise. + +{{< /admonition >}} + +To have access to full instance sync you must explicitly enable the option. + +The following applies: -- If you try to perform a full instance sync with resources that contain alerts or panels, the connection will be blocked. - You won't be able to create new alerts or library panels after setup is completed. - If you opted for full instance sync and want to use alerts and library panels, you'll have to delete the provisioned repository and connect again with folder sync. #### Set up synchronization -Choose to either sync your entire organization resources with external storage, or to sync certain resources to a new Grafana folder (with up to 10 connections). +You can sync external resources into a new folder without affecting the rest of your instance. -- Choose **Sync all resources with external storage** if you want to sync and manage your entire Grafana instance through external storage. With this option, all of your dashboards are synced to that one repository. You can only have one provisioned connection with this selection, and you won't have the option of setting up additional repositories to connect to. +To set up synchronization: -- Choose **Sync external storage to new Grafana folder** to sync external resources into a new folder without affecting the rest of your instance. You can repeat this process for up to 10 connections. +1. Select which resources you want to sync. -Next, enter a **Display name** for the repository connection. Resources stored in this connection appear under the chosen display name in the Grafana UI. +1. Enter a **Display name** for the repository connection. Resources stored in this connection appear under the chosen display name in the Grafana UI. -Click **Synchronize** to continue. +1. Click **Synchronize** to continue. + +1. You can repeat this process for up to 10 connections. ### Synchronize with external storage diff --git a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md index cd07c532dae..b43c279fb44 100644 --- a/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md +++ b/docs/sources/as-code/observability-as-code/provision-resources/git-sync-setup.md @@ -132,17 +132,35 @@ To connect your GitHub repository: ### Choose what to synchronize -In this step, you can decide which elements to synchronize. The available options depend on the status of your Grafana instance: - -- If the instance contains resources in an incompatible data format, you'll have to migrate all the data using instance sync. Folder sync won't be supported. -- If there's already another connection using folder sync, instance sync won't be offered. +You can sync external resources into a new folder without affecting the rest of your instance. To set up synchronization: -- Choose **Sync all resources with external storage** if you want to sync and manage your entire Grafana instance through external storage. With this option, all of your dashboards are synced to that one repository. You can only have one provisioned connection with this selection, and you won't have the option of setting up additional repositories to connect to. -- Choose **Sync external storage to new Grafana folder** to sync external resources into a new folder without affecting the rest of your instance. You can repeat this process for up to 10 connections. +1. Select which resources you want to sync. -Next, enter a **Display name** for the repository connection. Resources stored in this connection appear under the chosen display name in the Grafana UI. Click **Synchronize** to continue. +1. Enter a **Display name** for the repository connection. Resources stored in this connection appear under the chosen display name in the Grafana UI. + +1. Click **Synchronize** to continue. + +1. You can repeat this process for up to 10 connections. + +{{< admonition type="note" >}} + +Optionally, you can export any unmanaged resources into the provisioned folder. See how in [Synchronize with external storage](#synchronize-with-external-storage). + +{{< /admonition >}} + +#### Full instance sync + +Full instance sync is not available in Grafana Cloud and is experimental and unsupported in Grafana OSS/Enterprise. + +To have access to this option you must enable experimental instance sync on purpose. + +### Synchronize with external storage + +After this one time step, all future updates are automatically saved to the Git repository and provisioned back to the instance. + +Check the **Migrate existing resources** box to migrate your unmanaged dashboards to the provisioned folder. ### Choose additional settings diff --git a/docs/sources/as-code/observability-as-code/provision-resources/intro-git-sync.md b/docs/sources/as-code/observability-as-code/provision-resources/intro-git-sync.md index 9e32ce32e8a..903df6303a6 100644 --- a/docs/sources/as-code/observability-as-code/provision-resources/intro-git-sync.md +++ b/docs/sources/as-code/observability-as-code/provision-resources/intro-git-sync.md @@ -47,7 +47,7 @@ Using Git Sync, you can: {{< admonition type="caution" >}} -Git Sync only works with specific folders for the moment. Full-instance sync is not currently supported. +Full instance sync is not available in Grafana Cloud and is experimental and unsupported in Grafana OSS/Enterprise. {{< /admonition >}} @@ -84,7 +84,7 @@ Refer to [Requirements](https://grafana.com/docs/grafana//obser - You can only sync dashboards and folders. Refer to [Supported resources](#supported-resources) for more information. - If you're using Git Sync in Grafana OSS and Grafana Enterprise, some resources might be in an incompatible data format and won't be synced. -- Full-instance sync is not available in Grafana Cloud and has limitations in Grafana OSS and Grafana Enterprise. Refer to [Choose what to synchronize](../git-sync-setup/#choose-what-to-synchronize) for more details. +- Full-instance sync is not available in Grafana Cloud and is experimental in Grafana OSS and Grafana Enterprise. Refer to [Choose what to synchronize](../git-sync-setup/#choose-what-to-synchronize) for more details. - When migrating to full instance sync, during the synchronization process your resources will be temporarily unavailable. No one will be able to create, edit, or delete resources during this process. - If you want to manage existing resources with Git Sync, you need to save them as JSON files and commit them to the synced repository. Open a PR to import, copy, move, or save a dashboard. - Restoring resources from the UI is currently not possible. As an alternative, you can restore dashboards directly in your GitHub repository by raising a PR, and they will be updated in Grafana. From 338ae95ef53fa7f7f36cb754673ca4c5c92502d3 Mon Sep 17 00:00:00 2001 From: Renato Costa <103441181+renatolabs@users.noreply.github.com> Date: Fri, 19 Dec 2025 09:15:23 -0500 Subject: [PATCH 255/297] unified-storage: add `BatchDelete` support to sqlkv implementation (#115573) --- .../resource/data/sqlkv_batch_delete.sql | 7 ++ pkg/storage/unified/resource/sqlkv.go | 31 ++++++--- pkg/storage/unified/testing/kv.go | 65 ++++++++++--------- pkg/storage/unified/testing/kv_test.go | 1 - 4 files changed, 63 insertions(+), 41 deletions(-) create mode 100644 pkg/storage/unified/resource/data/sqlkv_batch_delete.sql diff --git a/pkg/storage/unified/resource/data/sqlkv_batch_delete.sql b/pkg/storage/unified/resource/data/sqlkv_batch_delete.sql new file mode 100644 index 00000000000..c165007d23d --- /dev/null +++ b/pkg/storage/unified/resource/data/sqlkv_batch_delete.sql @@ -0,0 +1,7 @@ +DELETE +FROM {{ .TableName }} +WHERE {{ .Ident "key_path" }} IN ( + {{ range $id, $key_path := .KeyPaths }} + {{ if ne $id 0 }}, {{ end }}{{ $.Arg $key_path }} + {{ end }} +); diff --git a/pkg/storage/unified/resource/sqlkv.go b/pkg/storage/unified/resource/sqlkv.go index 22cd3085122..3c07403296b 100644 --- a/pkg/storage/unified/resource/sqlkv.go +++ b/pkg/storage/unified/resource/sqlkv.go @@ -34,10 +34,11 @@ func mustTemplate(filename string) *template.Template { // Templates. var ( - sqlKVKeys = mustTemplate("sqlkv_keys.sql") - sqlKVGet = mustTemplate("sqlkv_get.sql") - sqlKVBatchGet = mustTemplate("sqlkv_batch_get.sql") - sqlKVDelete = mustTemplate("sqlkv_delete.sql") + sqlKVKeys = mustTemplate("sqlkv_keys.sql") + sqlKVGet = mustTemplate("sqlkv_get.sql") + sqlKVBatchGet = mustTemplate("sqlkv_batch_get.sql") + sqlKVDelete = mustTemplate("sqlkv_delete.sql") + sqlKVBatchDelete = mustTemplate("sqlkv_batch_delete.sql") ) // sqlKVSection can be embedded in structs used when rendering query templates @@ -108,17 +109,17 @@ func (req sqlKVGetRequest) Results() ([]byte, error) { return req.Value, nil } -type sqlKVBatchGetRequest struct { +type sqlKVBatchRequest struct { sqltemplate.SQLTemplate sqlKVSection Keys []string } -func (req sqlKVBatchGetRequest) Validate() error { +func (req sqlKVBatchRequest) Validate() error { return req.sqlKVSection.Validate() } -func (req sqlKVBatchGetRequest) KeyPaths() []string { +func (req sqlKVBatchRequest) KeyPaths() []string { result := make([]string, 0, len(req.Keys)) for _, key := range req.Keys { result = append(result, req.Section+"/"+key) @@ -250,7 +251,7 @@ func (k *sqlKV) BatchGet(ctx context.Context, section string, keys []string) ite return } - rows, err := dbutil.QueryRows(ctx, k.db, sqlKVBatchGet, sqlKVBatchGetRequest{ + rows, err := dbutil.QueryRows(ctx, k.db, sqlKVBatchGet, sqlKVBatchRequest{ SQLTemplate: sqltemplate.New(k.dialect), sqlKVSection: sqlKVSection{section}, Keys: keys, @@ -322,7 +323,19 @@ func (k *sqlKV) Delete(ctx context.Context, section string, key string) error { } func (k *sqlKV) BatchDelete(ctx context.Context, section string, keys []string) error { - panic("not implemented!") + if len(keys) == 0 { + return nil + } + + if _, err := dbutil.Exec(ctx, k.db, sqlKVBatchDelete, sqlKVBatchRequest{ + SQLTemplate: sqltemplate.New(k.dialect), + sqlKVSection: sqlKVSection{section}, + Keys: keys, + }); err != nil { + return fmt.Errorf("failed to batch delete keys: %w", err) + } + + return nil } func (k *sqlKV) UnixTimestamp(ctx context.Context) (int64, error) { diff --git a/pkg/storage/unified/testing/kv.go b/pkg/storage/unified/testing/kv.go index fbd831f6281..770bf9ac6e6 100644 --- a/pkg/storage/unified/testing/kv.go +++ b/pkg/storage/unified/testing/kv.go @@ -5,6 +5,8 @@ import ( "context" "fmt" "io" + "maps" + "slices" "strings" "testing" "time" @@ -786,35 +788,35 @@ func runTestKVBatchGet(t *testing.T, kv resource.KV, nsPrefix string) { func runTestKVBatchDelete(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) - section := nsPrefix + "-batchdelete" + nsPrefix += "-batchdelete" t.Run("batch delete existing keys", func(t *testing.T) { // Setup test data testData := map[string]string{ - "key1": "value1", - "key2": "value2", - "key3": "value3", + namespacedKey(nsPrefix, "key1"): "value1", + namespacedKey(nsPrefix, "key2"): "value2", + namespacedKey(nsPrefix, "key3"): "value3", } // Save test data for key, value := range testData { - saveKVHelper(t, kv, ctx, section, key, strings.NewReader(value)) + saveKVHelper(t, kv, ctx, testSection, key, strings.NewReader(value)) } // Verify keys exist before deletion for key := range testData { - _, err := kv.Get(ctx, section, key) + _, err := kv.Get(ctx, testSection, key) require.NoError(t, err) } // Batch delete all keys - keys := []string{"key1", "key2", "key3"} - err := kv.BatchDelete(ctx, section, keys) + keys := slices.Collect(maps.Keys(testData)) + err := kv.BatchDelete(ctx, testSection, keys) require.NoError(t, err) // Verify all keys are deleted for _, key := range keys { - _, err := kv.Get(ctx, section, key) + _, err := kv.Get(ctx, testSection, key) assert.Error(t, err) assert.Equal(t, resource.ErrNotFound, err) } @@ -822,39 +824,40 @@ func runTestKVBatchDelete(t *testing.T, kv resource.KV, nsPrefix string) { t.Run("batch delete with non-existent keys", func(t *testing.T) { // Setup some test data - saveKVHelper(t, kv, ctx, section, "existing-key-1", strings.NewReader("value1")) - saveKVHelper(t, kv, ctx, section, "existing-key-2", strings.NewReader("value2")) + key1, key2 := namespacedKey(nsPrefix, "existing-key-1"), namespacedKey(nsPrefix, "existing-key-2") + saveKVHelper(t, kv, ctx, testSection, key1, strings.NewReader("value1")) + saveKVHelper(t, kv, ctx, testSection, key2, strings.NewReader("value2")) // Batch delete with mix of existing and non-existent keys - keys := []string{"existing-key-1", "non-existent-1", "existing-key-2", "non-existent-2"} - err := kv.BatchDelete(ctx, section, keys) + keys := []string{key1, namespacedKey(nsPrefix, "non-existent-1"), key2, namespacedKey(nsPrefix, "non-existent-2")} + err := kv.BatchDelete(ctx, testSection, keys) require.NoError(t, err) // Verify existing keys are deleted - _, err = kv.Get(ctx, section, "existing-key-1") - assert.Error(t, err) + _, err = kv.Get(ctx, testSection, key1) + require.Error(t, err) assert.Equal(t, resource.ErrNotFound, err) - _, err = kv.Get(ctx, section, "existing-key-2") - assert.Error(t, err) + _, err = kv.Get(ctx, testSection, key2) + require.Error(t, err) assert.Equal(t, resource.ErrNotFound, err) }) t.Run("batch delete with all non-existent keys", func(t *testing.T) { // Batch delete keys that don't exist - keys := []string{"non-existent-1", "non-existent-2", "non-existent-3"} - err := kv.BatchDelete(ctx, section, keys) + keys := namespacedKeys(nsPrefix, []string{"non-existent-1", "non-existent-2", "non-existent-3"}) + err := kv.BatchDelete(ctx, testSection, keys) require.NoError(t, err) }) t.Run("batch delete with empty keys list", func(t *testing.T) { keys := []string{} - err := kv.BatchDelete(ctx, section, keys) + err := kv.BatchDelete(ctx, testSection, keys) require.NoError(t, err) }) t.Run("batch delete with empty section", func(t *testing.T) { - keys := []string{"some-key"} + keys := namespacedKeys(nsPrefix, []string{"some-key"}) err := kv.BatchDelete(ctx, "", keys) assert.Error(t, err) assert.Contains(t, err.Error(), "section is required") @@ -862,27 +865,27 @@ func runTestKVBatchDelete(t *testing.T, kv resource.KV, nsPrefix string) { t.Run("batch delete preserves other keys", func(t *testing.T) { // Setup test data - saveKVHelper(t, kv, ctx, section, "keep-key-1", strings.NewReader("keep-value-1")) - saveKVHelper(t, kv, ctx, section, "delete-key-1", strings.NewReader("delete-value-1")) - saveKVHelper(t, kv, ctx, section, "keep-key-2", strings.NewReader("keep-value-2")) - saveKVHelper(t, kv, ctx, section, "delete-key-2", strings.NewReader("delete-value-2")) + saveKVHelper(t, kv, ctx, testSection, namespacedKey(nsPrefix, "keep-key-1"), strings.NewReader("keep-value-1")) + saveKVHelper(t, kv, ctx, testSection, namespacedKey(nsPrefix, "delete-key-1"), strings.NewReader("delete-value-1")) + saveKVHelper(t, kv, ctx, testSection, namespacedKey(nsPrefix, "keep-key-2"), strings.NewReader("keep-value-2")) + saveKVHelper(t, kv, ctx, testSection, namespacedKey(nsPrefix, "delete-key-2"), strings.NewReader("delete-value-2")) // Batch delete specific keys - keys := []string{"delete-key-1", "delete-key-2"} - err := kv.BatchDelete(ctx, section, keys) + keys := namespacedKeys(nsPrefix, []string{"delete-key-1", "delete-key-2"}) + err := kv.BatchDelete(ctx, testSection, keys) require.NoError(t, err) // Verify deleted keys are gone - _, err = kv.Get(ctx, section, "delete-key-1") + _, err = kv.Get(ctx, testSection, namespacedKey(nsPrefix, "delete-key-1")) assert.Error(t, err) assert.Equal(t, resource.ErrNotFound, err) - _, err = kv.Get(ctx, section, "delete-key-2") + _, err = kv.Get(ctx, testSection, namespacedKey(nsPrefix, "delete-key-2")) assert.Error(t, err) assert.Equal(t, resource.ErrNotFound, err) // Verify kept keys still exist - reader, err := kv.Get(ctx, section, "keep-key-1") + reader, err := kv.Get(ctx, testSection, namespacedKey(nsPrefix, "keep-key-1")) require.NoError(t, err) value, err := io.ReadAll(reader) require.NoError(t, err) @@ -890,7 +893,7 @@ func runTestKVBatchDelete(t *testing.T, kv resource.KV, nsPrefix string) { err = reader.Close() require.NoError(t, err) - reader, err = kv.Get(ctx, section, "keep-key-2") + reader, err = kv.Get(ctx, testSection, namespacedKey(nsPrefix, "keep-key-2")) require.NoError(t, err) value, err = io.ReadAll(reader) require.NoError(t, err) diff --git a/pkg/storage/unified/testing/kv_test.go b/pkg/storage/unified/testing/kv_test.go index dafefc15ed2..3814df17b7e 100644 --- a/pkg/storage/unified/testing/kv_test.go +++ b/pkg/storage/unified/testing/kv_test.go @@ -50,7 +50,6 @@ func TestSQLKV(t *testing.T) { TestKVSave: true, TestKVConcurrent: true, TestKVUnixTimestamp: true, - TestKVBatchDelete: true, }, }) } From 133865182e125b73d902ce07176abdc89fffe084 Mon Sep 17 00:00:00 2001 From: Matheus Macabu Date: Fri, 19 Dec 2025 15:21:22 +0100 Subject: [PATCH 256/297] CI: Add e2e-playwright folder to e2e test detection changes (#115623) --- .github/actions/change-detection/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/change-detection/action.yml b/.github/actions/change-detection/action.yml index 863cab646b9..2b8484d7cf6 100644 --- a/.github/actions/change-detection/action.yml +++ b/.github/actions/change-detection/action.yml @@ -99,6 +99,7 @@ runs: - '${{ inputs.self }}' e2e: - 'e2e/**' + - 'e2e-playwright/**' - '.github/actions/setup-enterprise/**' - '.github/actions/checkout/**' - 'emails/**' From 62b2a202de1463cdfd3be0755b2805266d3b046a Mon Sep 17 00:00:00 2001 From: Deyan Halachliyski <119334180+dhalachliyski@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:32:27 +0100 Subject: [PATCH 257/297] Alerting: Add saved searches feature for alert rules page (#115001) * Alerting: Add saved searches feature for alert rules page Add ability to save, rename, delete, and apply search queries on the Alert Rules page. Includes auto-apply default search on navigation and UserStorage persistence. Behind feature toggle `alertingSavedSearches` (disabled by default). * Alerting: Add i18n translations for saved searches * Alerting: Remove unused imports in saved searches * Alerting: Add CODEOWNERS for e2e-playwright/alerting-suite * Alerting: Add useSavedSearches mock to RulesFilter.v2 tests * Alerting: Fix failing unit tests for saved searches - Fix Jest mock hoisting issue in useSavedSearches.test.ts by configuring UserStorage mock implementation after imports instead of inline - Update SavedSearches.test.tsx to use findBy* queries for async popup content - Fix tests to click apply button instead of text for applying searches - Update maxLength test to verify attribute instead of trying to exceed it * Alerting: Fix saved searches test mocking and assertions - Fix UserStorage mock in useSavedSearches.test.ts by creating mock with default Promise-returning functions inside jest.mock() factory, then accessing the instance via getMockUserStorageInstance() helper - Fix SavedSearches.test.tsx apply button tests to use correct accessible name "Apply this search" (from tooltip) instead of dynamic aria-label - Fix disabled button assertion to check native disabled attribute instead of relying on aria-disabled which is set inconsistently by Button component - Use findAllByRole for async popup content queries * Alerting: Fix test query for disabled save button Use findByText + closest instead of findByRole to find the disabled "Save current search" button. The Grafana Button component renders with conflicting accessibility attributes (disabled="" + aria-disabled="false") which breaks role-based queries in React Testing Library. * fix(alerting): preserve UserStorage mock reference before clearAllMocks * fix(alerting): add missing test mocks for crypto and console - Mock crypto.randomUUID for Node.js test environment - Add console.error spy to tests expecting storage/parse errors - Add console.warn spy to test expecting validation warnings Fixes jest-fail-on-console failures and crypto.randomUUID TypeError. * fix(alerting): add console.error spy to save failure test * fix(alerting): address PR review feedback for saved searches - Register alertingSavedSearches feature toggle in backend - Extract shared types to SavedSearches.types.ts to fix circular dependencies - Extract sub-components: InlineSaveInput, InlineRenameInput, SavedSearchItem - Remove unused imports (IconButton, Input) and styles from SavedSearches.tsx - Add try/catch for auto-apply default search error handling - Remove maxLength validation and corresponding test * fix(alerting): fix validation error display in saved searches - Fix useEffect dependency array that was immediately clearing validation errors - Remove error from deps so errors only clear when user types, not when set - Run i18n-extract to remove unused error-name-too-long translation key * fix(alerting): address PR review feedback for saved searches - Replace toHaveBeenCalled assertions with UI verification using AppNotificationList - Rename useSavedSearches.test.ts to .tsx for JSX support - Update README documentation to reflect current test patterns - Add test cleanup between E2E tests to prevent data leakage * fix(alerting): remove unused import and fix test wrapper - Remove unused locationService import from RulesFilter.v2.tsx - Add missing bootData spread in useSavedSearches.test.tsx mock - Add createWrapper to renderHook call for user-specific storage key test * fix(alerting): add Redux wrapper to all useSavedSearches hook tests All renderHook calls for useSavedSearches now include the createWrapper() which provides the Redux Provider context required by useAppNotification. * fix(alerting): use regex patterns in MSW handlers for UserStorage tests MSW handlers now use regex patterns to match any namespace and user UID, since UserStorage reads config values from internal imports that aren't affected by jest.mock of @grafana/runtime. * fix(alerting): mock UserStorage directly instead of using MSW Replace MSW HTTP handlers with a direct mock of the UserStorage class. The MSW approach failed because UserStorage evaluates config.namespace at module load time, before jest.mock takes effect, causing the regex patterns to not match the actual request URLs. This follows the same pattern used in useFavoriteDatasources.test.ts. * refactor(alerting): use react-hook-form and Dropdown for saved searches - Migrate InlineRenameInput and InlineSaveInput to react-hook-form - Replace custom PopupCard with Grafana Dropdown component - Use useReducer for centralized dropdown state management - Add stopPropagation handlers to prevent dropdown closing during form interactions - Update tests to use real useSavedSearches hook with mocked UserStorage - Consolidate and simplify saved searches test suite * fix: resolve CI failures in SavedSearches component - Fix TypeScript TS2540 errors by using MutableRefObject type for refs - Fix form submission by using onClick instead of type="submit" on IconButton (IconButton doesn't forward the type prop to the underlying button) - Fix action menu tests by stopping click propagation on ActionMenu wrapper - Fix Escape key handling by focusing the dialog element instead of the potentially-disabled save button * fix(alerting): add navTree to runtime mock in useSavedSearches tests Add empty navTree array to the @grafana/runtime config mock to prevent store initialization crash when buildInitialState() calls .find() on undefined navTree. * fix(alerting): add error handling for auto-apply default search Wrap handleApplySearch call in try-catch to prevent unhandled exceptions when auto-applying the default saved search on navigation. * fix(alerting): prevent saved searches dropdown from closing when clicking action menu The nested Dropdown components caused the outer SavedSearches dropdown to close when clicking on action menu items (Set as default, Rename, Delete). This happened because @floating-ui/react's useDismiss hook detected clicks on the inner Menu (rendered via Portal) as "outside" clicks. Fix: Replace the outer Dropdown with PopupCard and add custom click-outside handling that explicitly excludes portal elements ([role="menu"] and [data-popper-placement]). This matches the pattern used before the Dropdown refactor. Changes: - SavedSearches.tsx: Use PopupCard instead of Dropdown, add click-outside handler - SavedSearchItem.tsx: Add menuPortalRoot prop for action menu positioning - RulesFilter.v2.tsx: Fix double analytics tracking on auto-apply * fix(alerting): auto-apply default saved search on page navigation The default saved search was not being applied when navigating to the Alert rules page. This was caused by a race condition where `isLoading` was `false` on initial render (status was 'not-executed'), causing the auto-apply effect to run before saved searches were loaded. Fix: Include the uninitialized state in the loading check so the effect waits until data is actually loaded before attempting to auto-apply. Also adds tests for the auto-apply functionality. * fix(alerting): align action menu icon and improve saved search tests - Fix vertical alignment of three-dot menu icon in saved search items by adding flex centering to the wrapper div - Add feature toggle setup/teardown in saved searches test suite - Fix location mocking in test for URL search parameter handling * refactor(alerting): improve saved searches validation and organization - Rename SavedSearches.types.ts to savedSearchesSchema.ts - Use react-hook-form's built-in validation instead of manual setError - Change error handling to throw ValidationError instead of returning it - Add type guard isValidationError for safe error checking - Add alphabetical sorting for saved searches (default first) - Replace console.warn/error with logWarning/logError for analytics - Extract helper functions: sortSavedSearches, loadSavedSearchesFromStorage, hasUrlSearchQuery * refactor(alerting): address PR review comments for saved searches (steps 9-12) - Add comprehensive comment explaining useEffect double-render limitation and potential future improvements for default search auto-apply (step 9) - Add test documenting expected behavior when navigating back to alert list after leaving the page - default filter is re-applied (step 10) - Update RulesFilter.v2.test.tsx to use testWithFeatureToggles helper and add MSW UserStorage handlers for future use (step 11) - Update SavedSearches.test.tsx to use render from test/test-utils and byRole selectors for menu items (step 12) * test(alerting): update saved searches tests for refactored API - Update mockSavedSearches order to match sorted output (default first, then alphabetically) - Change validation error tests to use rejects pattern (saveSearch/renameSearch now throw) - Add hasPermission mock to contextSrv for module-level permission check * fix(alerting): fix CI failures for saved searches - Update onRenameComplete type to match throw-based API (Promise) - Run i18n-extract to add missing translation keys * fix(alerting): salvage valid entries when saved searches validation fails Instead of returning an empty array when array validation fails, iterate through each item and keep only the valid entries. This prevents losing all saved searches if a single entry is corrupted. * test(alerting): update test to expect valid entries to be preserved Update the test assertion to match the new behavior where valid saved search entries are preserved when some entries fail validation, rather than discarding all entries. * fix(alerting): eliminate double API request on saved search auto-apply Move saved searches loading and auto-apply logic from RulesFilterV2 to RuleListPage. This ensures the default search filter is applied BEFORE FilterView mounts, preventing double API requests on initial page load. - Load saved searches at RuleListPage level - Gate RuleList rendering until saved searches are loaded - Pass savedSearchesResult as prop to avoid duplicate hook calls - Remove auto-apply tests from RulesFilter.v2.test.tsx (behavior moved) * fix(alerting): mock useSavedSearches in RuleList.v2 tests The useSavedSearches hook triggers async state updates that complete after tests finish, causing React act() warnings. Mock the hook to prevent async operations during tests. * refactor(alerting): migrate saved searches tests to use MSW Address code review feedback by migrating UserStorage tests from jest.mock to MSW-based mocking: - Add MSW helper functions (setAlertingStorageItem, getAlertingStorageItem) to simplify test setup for UserStorage - Migrate useSavedSearches.test.tsx to use MSW handlers instead of jest.mock('@grafana/runtime/internal') - Migrate RulesFilter.v2.test.tsx to use MSW handlers - Update README documentation to accurately reflect how tests use MSW - Add tests for default search auto-apply behavior in RuleListPage - Simplify comments to be concise and accurate * fix(alerting): mock UserStorage directly in useSavedSearches tests The UserStorage class caches its storage spec at the instance level, and the useSavedSearches hook creates the instance at module level. This caused test isolation issues where cached state leaked between tests, making all tests that depended on loading data fail. Fix by mocking UserStorage class directly instead of relying on MSW handlers. This gives each test explicit control over what getItem and setItem return, ensuring proper isolation. Also update persistence assertions to verify mock.setItem calls instead of reading from MSW storage (which the mock bypasses). * refactor(alerting): remove setup helper in SavedSearches tests Replace the `setup()` helper function with direct `render()` calls as suggested in PR review. This makes tests more explicit about what component is being rendered and with what props. * refactor(alerting): extract default search auto-apply into dedicated hook Moves the default saved search auto-apply logic from useSavedSearches into a new useApplyDefaultSearch hook. This improves separation of concerns by keeping useSavedSearches focused on CRUD operations while the new hook handles the page-level auto-apply behavior. Key changes: - Created useApplyDefaultSearch hook with session-based visit tracking - Removed getAutoApplySearch method and user-specific session keys from useSavedSearches - Exported loadDefaultSavedSearch utility for independent default search loading - Simplified test mocks to use loadDefaultSavedSearch instead of full hook mocking - Removed unused savedSearchesResult prop passing through component tree * fix(alerting): improve default search auto-apply timing and test reliability Replace react-use's auto-executing useAsync with internal useAsync hook for better control over when default search is loaded. This prevents race conditions and ensures the async operation only executes when needed. Test improvements: - Add proper session storage cleanup in beforeEach - Use waitFor to handle async operations correctly - Prevent visited flag from affecting subsequent tests - Clear mock call history between tests The internal useAsync hook doesn't auto-execute on mount, allowing us to control exactly when the default search loads based on conditions rather than relying on dependency array triggers. --------- Co-authored-by: Konrad Lalik --- .github/CODEOWNERS | 1 + .../alerting-suite/saved-searches.spec.ts | 271 +++++++++ .../src/types/featureToggles.gen.ts | 4 + pkg/services/featuremgmt/registry.go | 7 + pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.json | 13 + .../alerting/unified/featureToggles.ts | 5 + .../app/features/alerting/unified/mockApi.ts | 2 + .../unified/mocks/server/all-handlers.ts | 2 + .../mocks/server/handlers/userStorage.ts | 171 ++++++ .../unified/rule-list/RuleList.v2.test.tsx | 146 ++++- .../unified/rule-list/RuleList.v2.tsx | 7 +- .../rule-list/filter/InlineRenameInput.tsx | 146 +++++ .../rule-list/filter/InlineSaveInput.tsx | 139 +++++ .../rule-list/filter/RulesFilter.v2.test.tsx | 117 ++-- .../rule-list/filter/RulesFilter.v2.tsx | 47 +- .../rule-list/filter/SavedSearchItem.tsx | 226 +++++++ .../rule-list/filter/SavedSearches.README.md | 404 +++++++++++++ .../rule-list/filter/SavedSearches.test.tsx | 348 +++++++++++ .../rule-list/filter/SavedSearches.tsx | 556 ++++++++++++++++++ .../rule-list/filter/savedSearchesSchema.ts | 87 +++ .../rule-list/filter/useApplyDefaultSearch.ts | 86 +++ .../filter/useSavedSearches.test.tsx | 400 +++++++++++++ .../rule-list/filter/useSavedSearches.ts | 353 +++++++++++ public/locales/en-US/grafana.json | 29 + 25 files changed, 3508 insertions(+), 60 deletions(-) create mode 100644 e2e-playwright/alerting-suite/saved-searches.spec.ts create mode 100644 public/app/features/alerting/unified/mocks/server/handlers/userStorage.ts create mode 100644 public/app/features/alerting/unified/rule-list/filter/InlineRenameInput.tsx create mode 100644 public/app/features/alerting/unified/rule-list/filter/InlineSaveInput.tsx create mode 100644 public/app/features/alerting/unified/rule-list/filter/SavedSearchItem.tsx create mode 100644 public/app/features/alerting/unified/rule-list/filter/SavedSearches.README.md create mode 100644 public/app/features/alerting/unified/rule-list/filter/SavedSearches.test.tsx create mode 100644 public/app/features/alerting/unified/rule-list/filter/SavedSearches.tsx create mode 100644 public/app/features/alerting/unified/rule-list/filter/savedSearchesSchema.ts create mode 100644 public/app/features/alerting/unified/rule-list/filter/useApplyDefaultSearch.ts create mode 100644 public/app/features/alerting/unified/rule-list/filter/useSavedSearches.test.tsx create mode 100644 public/app/features/alerting/unified/rule-list/filter/useSavedSearches.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4cac8f6dd81..7843dca6c87 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -425,6 +425,7 @@ i18next.config.ts @grafana/grafana-frontend-platform /public/locales/enterprise/i18next.config.ts @grafana/grafana-frontend-platform /public/app/core/internationalization/ @grafana/grafana-frontend-platform /e2e/ @grafana/grafana-frontend-platform +/e2e-playwright/alerting-suite/ @grafana/alerting-frontend /e2e-playwright/cloud-plugins-suite/ @grafana/partner-datasources /e2e-playwright/dashboard-new-layouts/ @grafana/dashboards-squad /e2e-playwright/dashboard-cujs/ @grafana/dashboards-squad diff --git a/e2e-playwright/alerting-suite/saved-searches.spec.ts b/e2e-playwright/alerting-suite/saved-searches.spec.ts new file mode 100644 index 00000000000..28de805a5a1 --- /dev/null +++ b/e2e-playwright/alerting-suite/saved-searches.spec.ts @@ -0,0 +1,271 @@ +import { Page } from '@playwright/test'; + +import { test, expect } from '@grafana/plugin-e2e'; + +/** + * UI selectors for Saved Searches e2e tests. + * Each selector is a function that takes the page and returns a locator. + */ +const ui = { + // Main elements + savedSearchesButton: (page: Page) => page.getByRole('button', { name: /saved searches/i }), + dropdown: (page: Page) => page.getByRole('dialog', { name: /saved searches/i }), + searchInput: (page: Page) => page.getByTestId('search-query-input'), + + // Save functionality + saveButton: (page: Page) => page.getByRole('button', { name: /save current search/i }), + saveConfirmButton: (page: Page) => page.getByRole('button', { name: /^save$/i }), + saveNameInput: (page: Page) => page.getByPlaceholder(/enter a name/i), + + // Action menu + actionsButton: (page: Page) => page.getByRole('button', { name: /actions/i }), + renameMenuItem: (page: Page) => page.getByText(/rename/i), + deleteMenuItem: (page: Page) => page.getByText(/^delete$/i), + setAsDefaultMenuItem: (page: Page) => page.getByText(/set as default/i), + deleteConfirmButton: (page: Page) => page.getByRole('button', { name: /^delete$/i }), + + // Indicators + emptyState: (page: Page) => page.getByText(/no saved searches/i), + defaultIcon: (page: Page) => page.locator('[title="Default search"]'), + duplicateError: (page: Page) => page.getByText(/already exists/i), +}; + +/** + * Helper to clear saved searches storage. + * UserStorage uses localStorage as fallback, so we clear both potential keys. + */ +async function clearSavedSearches(page: Page) { + await page.evaluate(() => { + // Clear localStorage keys that might contain saved searches + // UserStorage stores under 'grafana.userstorage.alerting' pattern + const keysToRemove = Object.keys(localStorage).filter( + (key) => key.includes('alerting') && (key.includes('savedSearches') || key.includes('userstorage')) + ); + keysToRemove.forEach((key) => localStorage.removeItem(key)); + + // Also clear session storage visited flag + const sessionKeysToRemove = Object.keys(sessionStorage).filter((key) => key.includes('alerting')); + sessionKeysToRemove.forEach((key) => sessionStorage.removeItem(key)); + }); +} + +test.describe( + 'Alert Rules - Saved Searches', + { + tag: ['@alerting'], + }, + () => { + test.beforeEach(async ({ page }) => { + // Clear any saved searches from previous tests before navigating + await page.goto('/alerting/list'); + await clearSavedSearches(page); + await page.reload(); + }); + + test.afterEach(async ({ page }) => { + // Clean up saved searches after each test + await clearSavedSearches(page); + }); + + test('should display Saved searches button', async ({ page }) => { + await expect(ui.savedSearchesButton(page)).toBeVisible(); + }); + + test('should open dropdown when clicking Saved searches button', async ({ page }) => { + await ui.savedSearchesButton(page).click(); + + await expect(ui.dropdown(page)).toBeVisible(); + }); + + test('should show empty state when no saved searches exist', async ({ page }) => { + // Storage is cleared in beforeEach, so we should see empty state + await ui.savedSearchesButton(page).click(); + + await expect(ui.emptyState(page)).toBeVisible(); + }); + + test('should enable Save current search button when search query is entered', async ({ page }) => { + // Enter a search query + await ui.searchInput(page).fill('state:firing'); + await ui.searchInput(page).press('Enter'); + + // Open saved searches + await ui.savedSearchesButton(page).click(); + + await expect(ui.saveButton(page)).toBeEnabled(); + }); + + test('should disable Save current search button when search query is empty', async ({ page }) => { + await ui.savedSearchesButton(page).click(); + + await expect(ui.saveButton(page)).toBeDisabled(); + }); + + test('should save a new search', async ({ page }) => { + // Enter a search query + await ui.searchInput(page).fill('state:firing'); + await ui.searchInput(page).press('Enter'); + + // Open saved searches + await ui.savedSearchesButton(page).click(); + + // Click save button + await ui.saveButton(page).click(); + + // Enter name and save + await ui.saveNameInput(page).fill('My Firing Rules'); + await ui.saveConfirmButton(page).click(); + + // Verify the saved search appears in the list + await expect(page.getByText('My Firing Rules')).toBeVisible(); + }); + + test('should show validation error for duplicate name', async ({ page }) => { + // First save a search + await ui.searchInput(page).fill('state:firing'); + await ui.searchInput(page).press('Enter'); + + await ui.savedSearchesButton(page).click(); + + await ui.saveButton(page).click(); + + await ui.saveNameInput(page).fill('Duplicate Test'); + await ui.saveConfirmButton(page).click(); + + // Try to save another with the same name + await ui.saveButton(page).click(); + await ui.saveNameInput(page).fill('Duplicate Test'); + await ui.saveConfirmButton(page).click(); + + // Verify validation error + await expect(ui.duplicateError(page)).toBeVisible(); + }); + + test('should apply a saved search', async ({ page }) => { + // Create a saved search first + await ui.searchInput(page).fill('state:firing'); + await ui.searchInput(page).press('Enter'); + + await ui.savedSearchesButton(page).click(); + + await ui.saveButton(page).click(); + + await ui.saveNameInput(page).fill('Apply Test'); + await ui.saveConfirmButton(page).click(); + + // Clear the search + await ui.searchInput(page).clear(); + await ui.searchInput(page).press('Enter'); + + // Apply the saved search + await ui.savedSearchesButton(page).click(); + await page.getByRole('button', { name: /apply search.*apply test/i }).click(); + + // Verify the search input is updated + await expect(ui.searchInput(page)).toHaveValue('state:firing'); + }); + + test('should rename a saved search', async ({ page }) => { + // Create a saved search + await ui.searchInput(page).fill('state:firing'); + await ui.searchInput(page).press('Enter'); + + await ui.savedSearchesButton(page).click(); + + await ui.saveButton(page).click(); + + await ui.saveNameInput(page).fill('Original Name'); + await ui.saveConfirmButton(page).click(); + + // Open action menu and click rename + await ui.actionsButton(page).click(); + await ui.renameMenuItem(page).click(); + + // Enter new name + const renameInput = page.getByDisplayValue('Original Name'); + await renameInput.clear(); + await renameInput.fill('Renamed Search'); + await page.keyboard.press('Enter'); + + // Verify the name was updated + await expect(page.getByText('Renamed Search')).toBeVisible(); + await expect(page.getByText('Original Name')).not.toBeVisible(); + }); + + test('should delete a saved search', async ({ page }) => { + // Create a saved search + await ui.searchInput(page).fill('state:firing'); + await ui.searchInput(page).press('Enter'); + + await ui.savedSearchesButton(page).click(); + + await ui.saveButton(page).click(); + + await ui.saveNameInput(page).fill('To Delete'); + await ui.saveConfirmButton(page).click(); + + // Verify it was saved + await expect(page.getByText('To Delete')).toBeVisible(); + + // Open action menu and click delete + await ui.actionsButton(page).click(); + await ui.deleteMenuItem(page).click(); + + // Confirm delete + await ui.deleteConfirmButton(page).click(); + + // Verify it was deleted + await expect(page.getByText('To Delete')).not.toBeVisible(); + }); + + test('should set a search as default', async ({ page }) => { + // Create a saved search + await ui.searchInput(page).fill('state:firing'); + await ui.searchInput(page).press('Enter'); + + await ui.savedSearchesButton(page).click(); + + await ui.saveButton(page).click(); + + await ui.saveNameInput(page).fill('Default Test'); + await ui.saveConfirmButton(page).click(); + + // Set as default + await ui.actionsButton(page).click(); + await ui.setAsDefaultMenuItem(page).click(); + + // Verify the star icon appears (indicating default) + await expect(ui.defaultIcon(page)).toBeVisible(); + }); + + test('should close dropdown when pressing Escape', async ({ page }) => { + await ui.savedSearchesButton(page).click(); + + await expect(ui.dropdown(page)).toBeVisible(); + + await page.keyboard.press('Escape'); + + await expect(ui.dropdown(page)).not.toBeVisible(); + }); + + test('should cancel save mode when pressing Escape', async ({ page }) => { + // Enter a search query + await ui.searchInput(page).fill('state:firing'); + await ui.searchInput(page).press('Enter'); + + await ui.savedSearchesButton(page).click(); + + // Start save mode + await ui.saveButton(page).click(); + + await expect(ui.saveNameInput(page)).toBeVisible(); + + // Press Escape to cancel + await page.keyboard.press('Escape'); + + // Verify we're back to list mode + await expect(ui.saveNameInput(page)).not.toBeVisible(); + await expect(ui.saveButton(page)).toBeVisible(); + }); + } +); diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 19a8fbf2c44..981b10dfb1c 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -535,6 +535,10 @@ export interface FeatureToggles { */ alertingListViewV2?: boolean; /** + * Enables saved searches for alert rules list + */ + alertingSavedSearches?: boolean; + /** * Disables the ability to send alerts to an external Alertmanager datasource. */ alertingDisableSendAlertsExternal?: boolean; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 7e876849dfe..d6f2bcbec2e 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -885,6 +885,13 @@ var ( Owner: grafanaAlertingSquad, FrontendOnly: true, }, + { + Name: "alertingSavedSearches", + Description: "Enables saved searches for alert rules list", + Stage: FeatureStageExperimental, + Owner: grafanaAlertingSquad, + FrontendOnly: true, + }, { Name: "alertingDisableSendAlertsExternal", Description: "Disables the ability to send alerts to an external Alertmanager datasource.", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 510c05a815b..179568aa0c4 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -122,6 +122,7 @@ suggestedDashboards,experimental,@grafana/sharing-squad,false,false,false dashboardTemplates,preview,@grafana/sharing-squad,false,false,false logsExploreTableDefaultVisualization,experimental,@grafana/observability-logs,false,false,true alertingListViewV2,privatePreview,@grafana/alerting-squad,false,false,true +alertingSavedSearches,experimental,@grafana/alerting-squad,false,false,true alertingDisableSendAlertsExternal,experimental,@grafana/alerting-squad,false,false,false preserveDashboardStateWhenNavigating,experimental,@grafana/dashboards-squad,false,false,false alertingCentralAlertHistory,experimental,@grafana/alerting-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 0db4a887a6a..42922ecf82d 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -498,6 +498,19 @@ "codeowner": "@grafana/alerting-squad" } }, + { + "metadata": { + "name": "alertingSavedSearches", + "resourceVersion": "1765453147546", + "creationTimestamp": "2025-12-11T11:39:07Z" + }, + "spec": { + "description": "Enables saved searches for alert rules list", + "stage": "experimental", + "codeowner": "@grafana/alerting-squad", + "frontend": true + } + }, { "metadata": { "name": "alertingTriage", diff --git a/public/app/features/alerting/unified/featureToggles.ts b/public/app/features/alerting/unified/featureToggles.ts index 7ca1747a8c5..15fa8e11fd8 100644 --- a/public/app/features/alerting/unified/featureToggles.ts +++ b/public/app/features/alerting/unified/featureToggles.ts @@ -26,3 +26,8 @@ export const shouldUseBackendFilters = () => config.featureToggles.alertingUIUse export const shouldUseFullyCompatibleBackendFilters = () => config.featureToggles.alertingUIUseFullyCompatBackendFilters ?? false; + +/** + * Saved searches feature - allows users to save and apply search queries on the Alert Rules page. + */ +export const shouldUseSavedSearches = () => config.featureToggles.alertingSavedSearches ?? false; diff --git a/public/app/features/alerting/unified/mockApi.ts b/public/app/features/alerting/unified/mockApi.ts index 614c8235dd0..67053ade5f6 100644 --- a/public/app/features/alerting/unified/mockApi.ts +++ b/public/app/features/alerting/unified/mockApi.ts @@ -9,6 +9,7 @@ import { setupAlertmanagerStatusMapDefaultState, } from 'app/features/alerting/unified/mocks/server/entities/alertmanagers'; import { resetRoutingTreeMap } from 'app/features/alerting/unified/mocks/server/entities/k8s/routingtrees'; +import { resetUserStorage } from 'app/features/alerting/unified/mocks/server/handlers/userStorage'; import { DashboardDTO } from 'app/types/dashboard'; import { FolderDTO } from 'app/types/folders'; import { @@ -256,6 +257,7 @@ export function setupMswServer() { setupAlertmanagerConfigMapDefaultState(); setupAlertmanagerStatusMapDefaultState(); resetRoutingTreeMap(); + resetUserStorage(); }); return server; diff --git a/public/app/features/alerting/unified/mocks/server/all-handlers.ts b/public/app/features/alerting/unified/mocks/server/all-handlers.ts index 4b0b0ac7249..c3a9db4ac5c 100644 --- a/public/app/features/alerting/unified/mocks/server/all-handlers.ts +++ b/public/app/features/alerting/unified/mocks/server/all-handlers.ts @@ -19,6 +19,7 @@ import allPluginHandlers from 'app/features/alerting/unified/mocks/server/handle import provisioningHandlers from 'app/features/alerting/unified/mocks/server/handlers/provisioning'; import searchHandlers from 'app/features/alerting/unified/mocks/server/handlers/search'; import silenceHandlers from 'app/features/alerting/unified/mocks/server/handlers/silences'; +import userStorageHandlers from 'app/features/alerting/unified/mocks/server/handlers/userStorage'; /** * All alerting-specific handlers that are required across tests @@ -55,6 +56,7 @@ const allHandlers = [ ...datasourcesHandlers, ...evalHandlers, ...pluginsHandlers, + ...userStorageHandlers, ]; export default allHandlers; diff --git a/public/app/features/alerting/unified/mocks/server/handlers/userStorage.ts b/public/app/features/alerting/unified/mocks/server/handlers/userStorage.ts new file mode 100644 index 00000000000..b2a162ea898 --- /dev/null +++ b/public/app/features/alerting/unified/mocks/server/handlers/userStorage.ts @@ -0,0 +1,171 @@ +import { HttpResponse, http } from 'msw'; + +import { config } from '@grafana/runtime'; + +/** + * UserStorage spec type matching the backend API response. + */ +interface UserStorageSpec { + data: { [key: string]: string }; +} + +/** + * In-memory storage for UserStorage mock data. + * This allows tests to set up and verify storage state. + */ +let userStorageData: Record = {}; + +/** + * Get the base URL for UserStorage API. + * Uses config.namespace which defaults to 'default' in tests. + */ +const getBaseUrl = () => `/apis/userstorage.grafana.app/v0alpha1/namespaces/${config.namespace}/user-storage`; + +/** + * Reset the in-memory storage. Call this in beforeEach to ensure clean test state. + */ +export function resetUserStorage(): void { + userStorageData = {}; +} + +/** + * Get the resource name for a given service, matching how UserStorage constructs it. + * This uses config.bootData.user to determine the user identifier. + * + * @param service - The service name (e.g., 'alerting') + * @returns The resource name in format `{service}:{userUID}` + * + * @example + * // In a test with config.bootData.user = { uid: '', id: 123 } + * const resourceName = getResourceName('alerting'); // 'alerting:123' + * + * // In a test with config.bootData.user = { uid: 'abc-123', id: 456 } + * const resourceName = getResourceName('alerting'); // 'alerting:abc-123' + */ +export function getResourceName(service: string): string { + const user = config.bootData?.user; + const userUID = user?.uid === '' || !user?.uid ? String(user?.id ?? 'anonymous') : user.uid; + return `${service}:${userUID}`; +} + +/** + * Convenience constant for the alerting service name. + * Use with getResourceName('alerting') or ALERTING_SERVICE directly. + */ +export const ALERTING_SERVICE = 'alerting'; + +/** + * Set up initial data in the UserStorage mock. + * @param resourceName - The resource name (e.g., 'alerting:123'). Use getResourceName() to construct this. + * @param key - The storage key + * @param value - The value to store + */ +export function setUserStorageItem(resourceName: string, key: string, value: string): void { + if (!userStorageData[resourceName]) { + userStorageData[resourceName] = { data: {} }; + } + userStorageData[resourceName].data[key] = value; +} + +/** + * Convenience function to set alerting storage items using the current config's user. + * This automatically constructs the resource name from config.bootData.user. + * + * @param key - The storage key (e.g., 'savedSearches') + * @param value - The value to store (will be stored as-is, caller should JSON.stringify if needed) + * + * @example + * // Set up saved searches for testing + * setAlertingStorageItem('savedSearches', JSON.stringify([{ id: '1', name: 'Test', query: 'state:firing', isDefault: false, createdAt: Date.now() }])); + */ +export function setAlertingStorageItem(key: string, value: string): void { + const resourceName = getResourceName(ALERTING_SERVICE); + setUserStorageItem(resourceName, key, value); +} + +/** + * Convenience function to get alerting storage items using the current config's user. + * + * @param key - The storage key (e.g., 'savedSearches') + * @returns The stored value or null if not found + */ +export function getAlertingStorageItem(key: string): string | null { + const resourceName = getResourceName(ALERTING_SERVICE); + return getUserStorageItem(resourceName, key); +} + +/** + * Get data from the UserStorage mock. + * @param resourceName - The resource name (e.g., 'alerting:123') + * @param key - The storage key + * @returns The stored value or null if not found + */ +export function getUserStorageItem(resourceName: string, key: string): string | null { + return userStorageData[resourceName]?.data[key] ?? null; +} + +/** + * Get the full storage spec for a resource. + * @param resourceName - The resource name + */ +export function getUserStorageSpec(resourceName: string): UserStorageSpec | null { + return userStorageData[resourceName] ?? null; +} + +/** + * MSW handler for GET UserStorage (retrieve stored data) + */ +const getUserStorageHandler = () => + http.get<{ resourceName: string }>(`${getBaseUrl()}/:resourceName`, ({ params }) => { + const spec = userStorageData[params.resourceName]; + + if (!spec) { + return HttpResponse.json({ message: 'Not found' }, { status: 404 }); + } + + return HttpResponse.json({ spec }); + }); + +/** + * MSW handler for POST UserStorage (create new storage) + */ +const createUserStorageHandler = () => + http.post(getBaseUrl(), async ({ request }) => { + const body = (await request.json()) as { + metadata: { name: string; labels: { user: string; service: string } }; + spec: UserStorageSpec; + }; + + const resourceName = body.metadata.name; + userStorageData[resourceName] = body.spec; + + return HttpResponse.json({ spec: body.spec }, { status: 201 }); + }); + +/** + * MSW handler for PATCH UserStorage (update existing storage) + */ +const patchUserStorageHandler = () => + http.patch<{ resourceName: string }>(`${getBaseUrl()}/:resourceName`, async ({ params, request }) => { + const body = (await request.json()) as { spec: UserStorageSpec }; + const resourceName = params.resourceName; + + if (!userStorageData[resourceName]) { + userStorageData[resourceName] = { data: {} }; + } + + // Merge the new data with existing data + userStorageData[resourceName].data = { + ...userStorageData[resourceName].data, + ...body.spec.data, + }; + + return HttpResponse.json({ spec: userStorageData[resourceName] }); + }); + +/** + * All UserStorage MSW handlers + */ +const handlers = [getUserStorageHandler(), createUserStorageHandler(), patchUserStorageHandler()]; + +export default handlers; diff --git a/public/app/features/alerting/unified/rule-list/RuleList.v2.test.tsx b/public/app/features/alerting/unified/rule-list/RuleList.v2.test.tsx index d4c445a2772..7985791cfa2 100644 --- a/public/app/features/alerting/unified/rule-list/RuleList.v2.test.tsx +++ b/public/app/features/alerting/unified/rule-list/RuleList.v2.test.tsx @@ -1,5 +1,5 @@ import { HttpResponse } from 'msw'; -import { render, testWithFeatureToggles } from 'test/test-utils'; +import { render, testWithFeatureToggles, waitFor } from 'test/test-utils'; import { byRole, byTestId } from 'testing-library-selector'; import { OrgRole } from '@grafana/data'; @@ -12,7 +12,8 @@ import { setGrafanaRuleGroupExportResolver } from '../mocks/server/configure'; import { alertingFactory } from '../mocks/server/db'; import { RulesFilter } from '../search/rulesSearchParser'; -import RuleList, { RuleListActions } from './RuleList.v2'; +import RuleListPage, { RuleListActions } from './RuleList.v2'; +import { loadDefaultSavedSearch } from './filter/useSavedSearches'; // This tests only checks if proper components are rendered, so we mock them // Both FilterView and GroupedView are tested in their own tests @@ -24,6 +25,30 @@ jest.mock('./GroupedView', () => ({ GroupedView: () =>
Grouped View
, })); +jest.mock('./filter/useSavedSearches', () => ({ + ...jest.requireActual('./filter/useSavedSearches'), + loadDefaultSavedSearch: jest.fn(), + useSavedSearches: jest.fn(() => ({ + savedSearches: [], + isLoading: false, + saveSearch: jest.fn(), + renameSearch: jest.fn(), + deleteSearch: jest.fn(), + setDefaultSearch: jest.fn(), + })), +})); + +const loadDefaultSavedSearchMock = loadDefaultSavedSearch as jest.MockedFunction; + +beforeEach(() => { + loadDefaultSavedSearchMock.mockResolvedValue(null); + // Clear session storage to ensure clean state for each test + // This prevents the "visited" flag from affecting subsequent tests + sessionStorage.clear(); + // Set the visited flag for non-default-search tests to prevent the hook from trying to load + sessionStorage.setItem('grafana.alerting.ruleList.visited', 'true'); +}); + const ui = { filterView: byTestId('filter-view'), groupedView: byTestId('grouped-view'), @@ -45,16 +70,16 @@ setupMswServer(); alertingFactory.dataSource.build({ name: 'Mimir', uid: 'mimir' }); alertingFactory.dataSource.build({ name: 'Prometheus', uid: 'prometheus' }); -describe('RuleList v2', () => { +describe('RuleListPage v2', () => { it('should show grouped view by default', () => { - render(); + render(); expect(ui.groupedView.get()).toBeInTheDocument(); expect(ui.filterView.query()).not.toBeInTheDocument(); }); it('should show grouped view when invalid view parameter is provided', () => { - render(, { + render(, { historyOptions: { initialEntries: ['/?view=invalid'], }, @@ -65,35 +90,35 @@ describe('RuleList v2', () => { }); it('should show list view when "view=list" URL parameter is present', () => { - render(, { historyOptions: { initialEntries: ['/?view=list'] } }); + render(, { historyOptions: { initialEntries: ['/?view=list'] } }); expect(ui.filterView.get()).toBeInTheDocument(); expect(ui.groupedView.query()).not.toBeInTheDocument(); }); it('should show grouped view when only group filter is applied', () => { - render(, { historyOptions: { initialEntries: ['/?search=group:cpu-usage'] } }); + render(, { historyOptions: { initialEntries: ['/?search=group:cpu-usage'] } }); expect(ui.groupedView.get()).toBeInTheDocument(); expect(ui.filterView.query()).not.toBeInTheDocument(); }); it('should show grouped view when only namespace filter is applied', () => { - render(, { historyOptions: { initialEntries: ['/?search=namespace:global'] } }); + render(, { historyOptions: { initialEntries: ['/?search=namespace:global'] } }); expect(ui.groupedView.get()).toBeInTheDocument(); expect(ui.filterView.query()).not.toBeInTheDocument(); }); it('should show grouped view when both group and namespace filters are applied', () => { - render(, { historyOptions: { initialEntries: ['/?search=group:cpu-usage namespace:global'] } }); + render(, { historyOptions: { initialEntries: ['/?search=group:cpu-usage namespace:global'] } }); expect(ui.groupedView.get()).toBeInTheDocument(); expect(ui.filterView.query()).not.toBeInTheDocument(); }); it('should show list view when group and namespace filters are combined with other filter types', () => { - render(, { + render(, { historyOptions: { initialEntries: ['/?search=group:cpu-usage namespace:global state:firing'] }, }); @@ -102,14 +127,14 @@ describe('RuleList v2', () => { }); it('should show grouped view when view parameter is empty', () => { - render(, { historyOptions: { initialEntries: ['/?view='] } }); + render(, { historyOptions: { initialEntries: ['/?view='] } }); expect(ui.groupedView.get()).toBeInTheDocument(); expect(ui.filterView.query()).not.toBeInTheDocument(); }); it('should show grouped view when search parameter is empty', () => { - render(, { historyOptions: { initialEntries: ['/?search='] } }); + render(, { historyOptions: { initialEntries: ['/?search='] } }); expect(ui.groupedView.get()).toBeInTheDocument(); expect(ui.filterView.query()).not.toBeInTheDocument(); @@ -125,28 +150,28 @@ describe('RuleList v2', () => { { filterType: 'ruleHealth', searchQuery: 'health:error' }, { filterType: 'contactPoint', searchQuery: 'contactPoint:slack' }, ])('should show list view when %s filter is applied', ({ filterType, searchQuery }) => { - render(, { historyOptions: { initialEntries: [`/?search=${encodeURIComponent(searchQuery)}`] } }); + render(, { historyOptions: { initialEntries: [`/?search=${encodeURIComponent(searchQuery)}`] } }); expect(ui.filterView.get()).toBeInTheDocument(); expect(ui.groupedView.query()).not.toBeInTheDocument(); }); it('should show list view when "view=list" URL parameter is present with group filter', () => { - render(, { historyOptions: { initialEntries: ['/?view=list&search=group:cpu-usage'] } }); + render(, { historyOptions: { initialEntries: ['/?view=list&search=group:cpu-usage'] } }); expect(ui.filterView.get()).toBeInTheDocument(); expect(ui.groupedView.query()).not.toBeInTheDocument(); }); it('should show list view when "view=list" URL parameter is present with namespace filter', () => { - render(, { historyOptions: { initialEntries: ['/?view=list&search=namespace:global'] } }); + render(, { historyOptions: { initialEntries: ['/?view=list&search=namespace:global'] } }); expect(ui.filterView.get()).toBeInTheDocument(); expect(ui.groupedView.query()).not.toBeInTheDocument(); }); it('should show list view when "view=list" URL parameter is present with both group and namespace filters', () => { - render(, { + render(, { historyOptions: { initialEntries: ['/?view=list&search=group:cpu-usage namespace:global'] }, }); @@ -342,10 +367,10 @@ describe('RuleListActions', () => { }); }); -describe('RuleList v2 - View switching', () => { +describe('RuleListPage v2 - View switching', () => { it('should preserve both group and namespace filters when switching from list view to grouped view', async () => { // Start with list view and both group and namespace filters - const { user } = render(, { + const { user } = render(, { historyOptions: { initialEntries: ['/?view=list&search=group:cpu-usage namespace:global'] }, }); expect(ui.filterView.get()).toBeInTheDocument(); @@ -365,7 +390,7 @@ describe('RuleList v2 - View switching', () => { it('should clear all filters when switching from list view to grouped view with group, namespace and other filters', async () => { // Start with list view with all types of filters - const { user } = render(, { + const { user } = render(, { historyOptions: { initialEntries: ['/?view=list&search=group:cpu-usage namespace:global state:firing rule:"test"'], }, @@ -385,3 +410,86 @@ describe('RuleList v2 - View switching', () => { expect(ui.modeSelector.list.query()).not.toBeChecked(); }); }); +describe('RuleListPage v2 - Default search auto-apply', () => { + // These tests verify that the default search is applied at the page level, + // BEFORE child components mount, preventing double API requests. + + testWithFeatureToggles({ enable: ['alertingListViewV2', 'alertingSavedSearches'] }); + + beforeEach(() => { + // Clear the visited flag so the hook detects this as a first visit + sessionStorage.removeItem('grafana.alerting.ruleList.visited'); + // Clear mock call history between tests + loadDefaultSavedSearchMock.mockClear(); + }); + + it('should apply default search before rendering child components', async () => { + const mockDefaultSearch = { + id: '1', + name: 'My Default', + query: 'state:firing', + isDefault: true, + createdAt: Date.now(), + }; + + // Mock loadDefaultSavedSearch to return a default search + loadDefaultSavedSearchMock.mockResolvedValue(mockDefaultSearch); + + render(); + + // Wait for loadDefaultSavedSearch to be called + await waitFor(() => { + expect(loadDefaultSavedSearchMock).toHaveBeenCalled(); + }); + + // Wait for the filter view to render with the applied search + await waitFor(() => { + expect(ui.filterView.get()).toBeInTheDocument(); + }); + + // Verify the search input shows the applied search query + expect(ui.searchInput.get()).toHaveValue('state:firing'); + }); + + it('should not apply default search when URL already has search parameter', async () => { + const mockDefaultSearch = { + id: '1', + name: 'My Default', + query: 'state:firing', + isDefault: true, + createdAt: Date.now(), + }; + + // loadDefaultSavedSearch should not be called when URL has search param + loadDefaultSavedSearchMock.mockResolvedValue(mockDefaultSearch); + + render(, { + historyOptions: { initialEntries: ['/?search=label:team=backend'] }, + }); + + // Wait for the component to render + await waitFor(() => { + expect(ui.searchInput.get()).toBeInTheDocument(); + }); + + // Should show the URL's search, not the default + expect(ui.searchInput.get()).toHaveValue('label:team=backend'); + + // Verify loadDefaultSavedSearch was not called because filters are already active + // The hook should not execute at all when hasActiveFilters is true + expect(loadDefaultSavedSearchMock).not.toHaveBeenCalled(); + }); + + it('should render normally when no default search exists', async () => { + loadDefaultSavedSearchMock.mockResolvedValue(null); + + render(); + + // Wait for the component to render after checking for default search + await waitFor(() => { + expect(ui.groupedView.get()).toBeInTheDocument(); + }); + + expect(ui.searchInput.get()).toHaveValue(''); + }); +}); diff --git a/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx b/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx index e828b00fa16..284bd6ad757 100644 --- a/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx +++ b/public/app/features/alerting/unified/rule-list/RuleList.v2.tsx @@ -18,6 +18,7 @@ import { FilterView } from './FilterView'; import { GroupedView } from './GroupedView'; import { RuleListPageTitle } from './RuleListPageTitle'; import RulesFilter from './filter/RulesFilter'; +import { useApplyDefaultSearch } from './filter/useApplyDefaultSearch'; function RuleList() { const { filterState } = useRulesFilter(); @@ -117,14 +118,16 @@ export function RuleListActions() { } export default function RuleListPage() { + const { isApplying } = useApplyDefaultSearch(); + return ( } - isLoading={false} + isLoading={isApplying} actions={} > - + {!isApplying && } ); } diff --git a/public/app/features/alerting/unified/rule-list/filter/InlineRenameInput.tsx b/public/app/features/alerting/unified/rule-list/filter/InlineRenameInput.tsx new file mode 100644 index 00000000000..ae2db9e7264 --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/filter/InlineRenameInput.tsx @@ -0,0 +1,146 @@ +import { css } from '@emotion/css'; +import { useEffect } from 'react'; +import { useForm } from 'react-hook-form'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { t } from '@grafana/i18n'; +import { Box, IconButton, Input, Stack, Text, useStyles2 } from '@grafana/ui'; + +import { useAppNotification } from '../../../../../core/copy/appNotification'; + +import { SavedSearch, isValidationError, validateSearchName } from './savedSearchesSchema'; + +// ============================================================================ +// Inline Rename Input (compact input with icon buttons for renaming) +// ============================================================================ + +export interface InlineRenameInputProps { + initialValue: string; + /** Callback to save the renamed search. Throws ValidationError on validation failure. */ + onSave: (name: string) => Promise; + onCancel: () => void; + savedSearches: SavedSearch[]; + excludeId: string; +} + +interface FormValues { + name: string; +} + +export function InlineRenameInput({ + initialValue, + onSave, + onCancel, + savedSearches, + excludeId, +}: InlineRenameInputProps) { + const styles = useStyles2(getStyles); + const notifyApp = useAppNotification(); + + const { + register, + handleSubmit, + setFocus, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { name: initialValue }, + }); + + // Focus and select input on mount using react-hook-form's setFocus + useEffect(() => { + setFocus('name', { shouldSelect: true }); + }, [setFocus]); + + const onSubmit = async (data: FormValues) => { + try { + await onSave(data.name.trim()); + } catch (error) { + // Check if it's a validation error (has field and message) + if (isValidationError(error)) { + // Validation errors are shown inline in the form + // This is handled by react-hook-form validation, but we keep this + // as a fallback for server-side validation errors + return; + } + // For generic save operation errors, show a notification + notifyApp.error( + t('alerting.saved-searches.error-rename-title', 'Failed to rename'), + t('alerting.saved-searches.error-rename-description', 'Your changes could not be saved. Please try again.') + ); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + e.preventDefault(); + onCancel(); + } + }; + + return ( + +
+ + {/* Input area - flex=1 like the name area in list items */} + + { + const error = validateSearchName(value, savedSearches, excludeId); + return error ?? true; + }, + })} + onKeyDown={handleKeyDown} + placeholder={t('alerting.saved-searches.name-placeholder', 'Enter a name...')} + invalid={!!errors.name} + disabled={isSubmitting} + /> + + + {/* X icon - cancel */} + + + {/* Check icon - confirm rename */} + {/* Note: IconButton doesn't forward type="submit", so we use onClick with handleSubmit */} + + + + {errors.name?.message && ( + + {errors.name.message} + + )} +
+ ); +} + +// ============================================================================ +// Styles +// ============================================================================ + +function getStyles(theme: GrafanaTheme2) { + return { + successIcon: css({ + color: theme.colors.success.main, + }), + }; +} diff --git a/public/app/features/alerting/unified/rule-list/filter/InlineSaveInput.tsx b/public/app/features/alerting/unified/rule-list/filter/InlineSaveInput.tsx new file mode 100644 index 00000000000..9fd11151cd0 --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/filter/InlineSaveInput.tsx @@ -0,0 +1,139 @@ +import { css } from '@emotion/css'; +import { useEffect } from 'react'; +import { useForm } from 'react-hook-form'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { t } from '@grafana/i18n'; +import { Box, IconButton, Input, Stack, Text, useStyles2 } from '@grafana/ui'; + +import { useAppNotification } from '../../../../../core/copy/appNotification'; + +import { SavedSearch, isValidationError, validateSearchName } from './savedSearchesSchema'; + +// ============================================================================ +// Inline Save Input (compact input with icon buttons) +// ============================================================================ + +export interface InlineSaveInputProps { + /** Callback to save the search. Throws ValidationError on validation failure. */ + onSave: (name: string) => Promise; + onCancel: () => void; + savedSearches: SavedSearch[]; +} + +interface FormValues { + name: string; +} + +export function InlineSaveInput({ onSave, onCancel, savedSearches }: InlineSaveInputProps) { + const styles = useStyles2(getStyles); + const notifyApp = useAppNotification(); + + const { + register, + handleSubmit, + setFocus, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { name: '' }, + }); + + // Focus input on mount using react-hook-form's setFocus + useEffect(() => { + setFocus('name'); + }, [setFocus]); + + const onSubmit = async (data: FormValues) => { + try { + await onSave(data.name.trim()); + } catch (error) { + // Check if it's a validation error (has field and message) + if (isValidationError(error)) { + // Validation errors are shown inline in the form + // This is handled by react-hook-form validation, but we keep this + // as a fallback for server-side validation errors + return; + } + // For generic save operation errors, show a notification + notifyApp.error( + t('alerting.saved-searches.error-save-title', 'Failed to save'), + t('alerting.saved-searches.error-save-description', 'Your changes could not be saved. Please try again.') + ); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + e.preventDefault(); + onCancel(); + } + }; + + return ( + + {/* Match exact structure of SavedSearchItem: [flex-1 content] [icon] [icon] with gap={1} */} +
+ + {/* Input area - flex=1 like the name area in list items */} + + { + const error = validateSearchName(value, savedSearches); + return error ?? true; + }, + })} + onKeyDown={handleKeyDown} + placeholder={t('alerting.saved-searches.name-placeholder', 'Enter a name...')} + invalid={!!errors.name} + disabled={isSubmitting} + /> + + + {/* X icon - aligned with magnifying glass */} + + + {/* Check icon - aligned with action menu */} + {/* Note: IconButton doesn't forward type="submit", so we use onClick with handleSubmit */} + + + + {errors.name?.message && ( + + {errors.name.message} + + )} +
+ ); +} + +// ============================================================================ +// Styles +// ============================================================================ + +function getStyles(theme: GrafanaTheme2) { + return { + successIcon: css({ + color: theme.colors.success.main, + }), + }; +} diff --git a/public/app/features/alerting/unified/rule-list/filter/RulesFilter.v2.test.tsx b/public/app/features/alerting/unified/rule-list/filter/RulesFilter.v2.test.tsx index a8f6c91cbc3..8da5901a4b8 100644 --- a/public/app/features/alerting/unified/rule-list/filter/RulesFilter.v2.test.tsx +++ b/public/app/features/alerting/unified/rule-list/filter/RulesFilter.v2.test.tsx @@ -1,8 +1,8 @@ -import { render, screen } from 'test/test-utils'; +import { render, screen, testWithFeatureToggles, waitFor } from 'test/test-utils'; import { byRole, byTestId } from 'testing-library-selector'; import { ComponentTypeWithExtensionMeta, PluginExtensionComponentMeta, PluginExtensionTypes } from '@grafana/data'; -import { config, locationService, setPluginComponentsHook } from '@grafana/runtime'; +import { locationService, setPluginComponentsHook } from '@grafana/runtime'; import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { grantUserPermissions } from 'app/features/alerting/unified/mocks'; import { AccessControlAction } from 'app/types/accessControl'; @@ -15,6 +15,51 @@ import { setupPluginsExtensionsHook } from '../../testSetup/plugins'; import RulesFilter from './RulesFilter'; +// Mock config for UserStorage (namespace and user must be set before UserStorage module loads) +// This allows the real UserStorage class to work with MSW handlers +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + reportInteraction: jest.fn(), // Silence analytics calls from useSavedSearches + getDataSourceSrv: () => ({ + getList: jest.fn().mockReturnValue([ + { name: 'Prometheus', uid: 'prometheus-uid' }, + { name: 'Loki', uid: 'loki-uid' }, + ]), + }), + config: { + ...jest.requireActual('@grafana/runtime').config, + namespace: 'default', + bootData: { + ...jest.requireActual('@grafana/runtime').config.bootData, + navTree: [], + user: { + uid: 'test-user-123', + id: 123, + isSignedIn: true, + }, + }, + }, +})); + +// Set up contextSrv.user.id for useSavedSearches session storage key. +// The hook uses this ID to create a per-user session storage key. +// Note: hasPermission must be mocked here because RulesFilter.v1.tsx calls it at module load time, +// before grantUserPermissions can set up the spy. grantUserPermissions still works for runtime checks. +jest.mock('app/core/services/context_srv', () => { + const actual = jest.requireActual('app/core/services/context_srv'); + return { + ...actual, + contextSrv: { + ...actual.contextSrv, + user: { + ...actual.contextSrv.user, + id: 123, + }, + hasPermission: jest.fn().mockReturnValue(true), + }, + }; +}); + // Grant permission before importing the component since permission check happens at module level grantUserPermissions([AccessControlAction.AlertingReceiversRead]); // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -46,6 +91,7 @@ jest.mock('../../hooks/useFilteredRules', () => ({ const useRulesFilterMock = useRulesFilter as jest.MockedFunction; +// Set up MSW server with UserStorage handlers setupMswServer(); jest.spyOn(analytics, 'trackFilterButtonClick'); @@ -54,16 +100,6 @@ jest.spyOn(analytics, 'trackFilterButtonClearClick'); jest.spyOn(analytics, 'trackAlertRuleFilterEvent'); jest.spyOn(analytics, 'trackRulesSearchInputCleared'); -jest.mock('@grafana/runtime', () => ({ - ...jest.requireActual('@grafana/runtime'), - getDataSourceSrv: () => ({ - getList: jest.fn().mockReturnValue([ - { name: 'Prometheus', uid: 'prometheus-uid' }, - { name: 'Loki', uid: 'loki-uid' }, - ]), - }), -})); - jest.mock('../../components/rules/MultipleDataSourcePicker', () => { const original = jest.requireActual('../../components/rules/MultipleDataSourcePicker'); return { @@ -113,6 +149,8 @@ const ui = { beforeEach(() => { locationService.replace({ search: '' }); jest.clearAllMocks(); + sessionStorage.clear(); + localStorage.clear(); mockFilterState = { ruleName: '', @@ -150,45 +188,46 @@ beforeEach(() => { }); describe('RulesFilter Feature Flag', () => { - const originalFeatureToggle = config.featureToggles.alertingFilterV2; + describe('with alertingFilterV2 enabled', () => { + testWithFeatureToggles({ enable: ['alertingFilterV2'] }); - afterEach(() => { - config.featureToggles.alertingFilterV2 = originalFeatureToggle; + it('Should render RulesFilterV2 when alertingFilterV2 feature flag is enabled', async () => { + render(); + + // Wait for suspense to resolve and check that the V2 filter button is present + await screen.findByRole('button', { name: 'Filter' }); + expect(ui.filterButton.get()).toBeInTheDocument(); + expect(ui.searchInput.get()).toBeInTheDocument(); + }); }); - it('Should render RulesFilterV2 when alertingFilterV2 feature flag is enabled', async () => { - config.featureToggles.alertingFilterV2 = true; + describe('with alertingFilterV2 disabled', () => { + testWithFeatureToggles({ disable: ['alertingFilterV2'] }); - render(); + it('Should render RulesFilterV1 when alertingFilterV2 feature flag is disabled', async () => { + render(); - // Wait for suspense to resolve and check that the V2 filter button is present - await screen.findByRole('button', { name: 'Filter' }); - expect(ui.filterButton.get()).toBeInTheDocument(); - expect(ui.searchInput.get()).toBeInTheDocument(); - }); + // Wait for suspense to resolve and check V1 structure + await screen.findByText('Search'); - it('Should render RulesFilterV1 when alertingFilterV2 feature flag is disabled', async () => { - config.featureToggles.alertingFilterV2 = false; + // V1 has search input but no V2-style filter button + expect(ui.searchInput.get()).toBeInTheDocument(); + expect(ui.filterButton.query()).not.toBeInTheDocument(); - render(); - - // Wait for suspense to resolve and check V1 structure - await screen.findByText('Search'); - - // V1 has search input but no V2-style filter button - expect(ui.searchInput.get()).toBeInTheDocument(); - expect(ui.filterButton.query()).not.toBeInTheDocument(); - - // V1 has a help icon next to the search input - expect(screen.getByText('Search')).toBeInTheDocument(); + // V1 has a help icon next to the search input + expect(screen.getByText('Search')).toBeInTheDocument(); + }); }); }); describe('RulesFilterV2', () => { - it('Should render component without crashing', () => { + it('Should render component without crashing', async () => { render(); - expect(ui.searchInput.get()).toBeInTheDocument(); + // Wait for async hook operations (useSavedSearches) to complete + await waitFor(() => { + expect(ui.searchInput.get()).toBeInTheDocument(); + }); expect(ui.filterButton.get()).toBeInTheDocument(); }); @@ -429,4 +468,6 @@ describe('RulesFilterV2', () => { expect(analytics.trackFilterButtonClick).toHaveBeenCalledTimes(1); }); }); + + // Auto-apply of default search is tested in RuleList.v2.test.tsx (behavior is in RuleListPage) }); diff --git a/public/app/features/alerting/unified/rule-list/filter/RulesFilter.v2.tsx b/public/app/features/alerting/unified/rule-list/filter/RulesFilter.v2.tsx index 0091cdddec9..eb2e368ad80 100644 --- a/public/app/features/alerting/unified/rule-list/filter/RulesFilter.v2.tsx +++ b/public/app/features/alerting/unified/rule-list/filter/RulesFilter.v2.tsx @@ -39,10 +39,14 @@ import { useLabelOptions, useNamespaceAndGroupOptions, } from '../../components/rules/Filter/useRuleFilterAutocomplete'; +import { shouldUseSavedSearches } from '../../featureToggles'; import { useRulesFilter } from '../../hooks/useFilteredRules'; import { RuleHealth, RuleSource, getSearchFilterFromQuery } from '../../search/rulesSearchParser'; import { RulesFilterProps } from './RulesFilter'; +import { SavedSearches } from './SavedSearches'; +import { SavedSearch } from './savedSearchesSchema'; +import { trackSavedSearchApplied, useSavedSearches } from './useSavedSearches'; import { emptyAdvancedFilters, formAdvancedFiltersToRuleFilter, @@ -86,6 +90,19 @@ export default function RulesFilter({ viewMode, onViewModeChange }: RulesFilterP const popupRef = useRef(null); const { pluginsFilterEnabled } = usePluginsFilterStatus(); + // Feature toggle for saved searches + const savedSearchesEnabled = shouldUseSavedSearches(); + + // Saved searches hook with UserStorage persistence + const { + savedSearches, + isLoading: savedSearchesLoading, + saveSearch, + renameSearch, + deleteSearch, + setDefaultSearch, + } = useSavedSearches(); + // this form will managed the search query string, which is updated either by the user typing in the input or by the advanced filters const { control, setValue, handleSubmit } = useForm({ defaultValues: { @@ -97,6 +114,20 @@ export default function RulesFilter({ viewMode, onViewModeChange }: RulesFilterP setValue('query', searchQuery); }, [searchQuery, setValue]); + // Apply saved search - triggers filtering (which updates search input and URL) + const handleApplySearch = useCallback( + (search: SavedSearch) => { + const parsedFilter = getSearchFilterFromQuery(search.query); + updateFilters(parsedFilter); + + // Track analytics + trackSavedSearchApplied(search); + }, + [updateFilters] + ); + + // Auto-apply of default search is handled in RuleListPage (before FilterView mounts) + const submitHandler: SubmitHandler = (values: SearchQueryForm) => { const parsedFilter = getSearchFilterFromQuery(values.query); trackAlertRuleFilterEvent({ filterMethod: 'search-input', filter: parsedFilter, filterVariant: 'v2' }); @@ -240,7 +271,21 @@ export default function RulesFilter({ viewMode, onViewModeChange }: RulesFilterP {filterButtonLabel} - + {savedSearchesEnabled && ( + + )} + + +
diff --git a/public/app/features/alerting/unified/rule-list/filter/SavedSearchItem.tsx b/public/app/features/alerting/unified/rule-list/filter/SavedSearchItem.tsx new file mode 100644 index 00000000000..0fb92622b2d --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/filter/SavedSearchItem.tsx @@ -0,0 +1,226 @@ +import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { t } from '@grafana/i18n'; +import { Dropdown, Icon, IconButton, Menu, Stack, Text, useStyles2 } from '@grafana/ui'; + +import { InlineRenameInput } from './InlineRenameInput'; +import { SavedSearch } from './savedSearchesSchema'; + +// ============================================================================ +// Saved Search Item +// ============================================================================ + +export interface SavedSearchItemProps { + search: SavedSearch; + isRenaming: boolean; + isDeleting: boolean; + isDisabled: boolean; + onApply: () => void; + onStartRename: () => void; + onCancelRename: () => void; + onRenameComplete: (newName: string) => Promise; + onStartDelete: () => void; + onCancelDelete: () => void; + onDeleteConfirm: () => Promise; + onSetDefault: () => void; + savedSearches: SavedSearch[]; + /** Portal root for the action menu - should be the outer dropdown container */ + menuPortalRoot?: HTMLElement | null; +} + +export function SavedSearchItem({ + search, + isRenaming, + isDeleting, + isDisabled, + onApply, + onStartRename, + onCancelRename, + onRenameComplete, + onStartDelete, + onCancelDelete, + onDeleteConfirm, + onSetDefault, + savedSearches, + menuPortalRoot, +}: SavedSearchItemProps) { + const styles = useStyles2(getStyles); + + // Rename mode - inline form matching the save form + // Stop propagation to prevent parent Dropdown from closing when interacting with form + if (isRenaming) { + return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions +
e.stopPropagation()}> + +
+ ); + } + + // Delete confirm mode - inline with name visible + // Stop propagation to prevent parent Dropdown from closing when interacting with delete confirmation + if (isDeleting) { + return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions +
e.stopPropagation()}> + + {/* Name remains visible */} + + {search.name} + + + {/* X icon - cancel delete */} + + + {/* Trash icon - confirm delete */} + + +
+ ); + } + + // Default display mode + return ( +
+ + {/* Name and default indicator */} + + {search.name} + {search.isDefault && ( + + )} + + + {/* Apply button (magnifying glass) */} + + + {/* Action menu - stop propagation to prevent parent Dropdown from closing */} + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} +
e.stopPropagation()}> + +
+
+
+ ); +} + +// ============================================================================ +// Action Menu (three-dot menu) +// ============================================================================ + +interface ActionMenuProps { + isDefault: boolean; + isDisabled: boolean; + onSetDefault: () => void; + onRename: () => void; + onDelete: () => void; + /** Portal root for the menu - renders inside the outer dropdown to prevent useDismiss issues */ + portalRoot?: HTMLElement | null; +} + +function ActionMenu({ isDefault, isDisabled, onSetDefault, onRename, onDelete, portalRoot }: ActionMenuProps) { + const menu = ( + + + + + + + ); + + return ( + + + + ); +} + +// ============================================================================ +// Styles +// ============================================================================ + +function getStyles(theme: GrafanaTheme2) { + return { + item: css({ + padding: theme.spacing(0.5), + borderRadius: theme.shape.radius.default, + '&:hover': { + backgroundColor: theme.colors.action.hover, + }, + }), + defaultIcon: css({ + color: theme.colors.warning.main, + flexShrink: 0, + }), + deleteIcon: css({ + color: theme.colors.error.main, + }), + actionMenuWrapper: css({ + display: 'flex', + alignItems: 'center', + }), + }; +} diff --git a/public/app/features/alerting/unified/rule-list/filter/SavedSearches.README.md b/public/app/features/alerting/unified/rule-list/filter/SavedSearches.README.md new file mode 100644 index 00000000000..8935cecaaa1 --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/filter/SavedSearches.README.md @@ -0,0 +1,404 @@ +# Saved Searches Feature + +The Saved Searches feature allows users to save, manage, and quickly apply search queries on the Alert Rules page. + +## Overview + +Users can: + +- **Save** the current search query with a custom name +- **Apply** a saved search to instantly filter rules +- **Rename** existing saved searches +- **Delete** saved searches they no longer need +- **Set a default** search that auto-applies when navigating to the page + +## Components + +### `` + +The main component that renders a button and dropdown for managing saved searches. + +```tsx +import { SavedSearches } from './SavedSearches'; + +; +``` + +#### Props + +| Prop | Type | Required | Description | +| -------------------- | ------------------------------------------------------------------- | -------- | ------------------------------------------- | +| `savedSearches` | `SavedSearch[]` | Yes | Array of saved search objects | +| `currentSearchQuery` | `string` | Yes | The current search query in the input field | +| `onSave` | `(name: string, query: string) => Promise` | Yes | Called when user saves a new search | +| `onRename` | `(id: string, newName: string) => Promise` | Yes | Called when user renames a search | +| `onDelete` | `(id: string) => Promise` | Yes | Called when user deletes a search | +| `onApply` | `(search: SavedSearch) => void` | Yes | Called when user applies a search | +| `onSetDefault` | `(id: string \| null) => Promise` | Yes | Called when user sets/clears default | + +#### Types + +```typescript +interface SavedSearch { + /** Unique identifier */ + id: string; + /** User-provided name */ + name: string; + /** The search query string */ + query: string; + /** Whether this is the default search */ + isDefault: boolean; + /** Unix timestamp of creation */ + createdAt: number; +} + +interface ValidationError { + /** The field with the error */ + field: string; + /** Error message to display */ + message: string; +} +``` + +### `useSavedSearches()` Hook + +A custom hook that manages saved searches with UserStorage persistence. + +```tsx +import { useSavedSearches, trackSavedSearchApplied } from './useSavedSearches'; + +const { savedSearches, isLoading, saveSearch, renameSearch, deleteSearch, setDefaultSearch, getAutoApplySearch } = + useSavedSearches(); + +// Track when a search is applied +const handleApply = (search: SavedSearch) => { + applySearchToFilter(search.query); + trackSavedSearchApplied(search); +}; +``` + +#### Return Value + +| Property | Type | Description | +| -------------------- | --------------------------------------------------- | ------------------------------------- | +| `savedSearches` | `SavedSearch[]` | Current list of saved searches | +| `isLoading` | `boolean` | Whether initial load is in progress | +| `saveSearch` | `(name, query) => Promise` | Save a new search | +| `renameSearch` | `(id, newName) => Promise` | Rename an existing search | +| `deleteSearch` | `(id) => Promise` | Delete a search | +| `setDefaultSearch` | `(id \| null) => Promise` | Set or clear the default search | +| `getAutoApplySearch` | `() => SavedSearch \| null` | Get the default search for auto-apply | + +## Integration + +### Basic Integration + +```tsx +import { SavedSearches, SavedSearch } from './SavedSearches'; +import { useSavedSearches, trackSavedSearchApplied } from './useSavedSearches'; + +function MyFilterComponent() { + const { filterState, updateFilters } = useMyFilter(); + + const { savedSearches, saveSearch, renameSearch, deleteSearch, setDefaultSearch, getAutoApplySearch } = + useSavedSearches(); + + // Handle applying a saved search + const handleApply = useCallback( + (search: SavedSearch) => { + // Update your filter state with the saved query + updateFilters(parseQuery(search.query)); + + // Track analytics + trackSavedSearchApplied(search); + }, + [updateFilters] + ); + + // Auto-apply default search on navigation + useEffect(() => { + const defaultSearch = getAutoApplySearch(); + if (defaultSearch) { + handleApply(defaultSearch); + } + }, [getAutoApplySearch, handleApply]); + + return ( + + ); +} +``` + +### Feature Toggle + +The feature is gated behind the `alertingSavedSearches` feature toggle: + +```tsx +import { shouldUseSavedSearches } from '../../featureToggles'; + +function MyComponent() { + const savedSearchesEnabled = shouldUseSavedSearches(); + + return ( + <> + {savedSearchesEnabled && } + + ); +} +``` + +To enable during development, set in Grafana config: + +```ini +[feature_toggles] +alertingSavedSearches = true +``` + +## Behavior + +### Dropdown States + +1. **List Mode** (default) + - Shows saved searches sorted: default first, then alphabetical + - Shows "Save current search" button when `currentSearchQuery` is non-empty + - Empty state when no saved searches exist + +2. **Save Mode** + - Name input with validation + - Save/Cancel buttons + - Triggered by clicking "Save current search" + +3. **Rename Mode** (per-item) + - Inline editing of search name + - Confirm with Enter, cancel with Escape + +4. **Delete Confirmation** (per-item) + - Inline confirmation prompt + - Delete/Cancel buttons + +### Validation Rules + +| Rule | Message | +| ------------------------------ | ---------------------------------------------- | +| Name required | "Name is required" | +| Max length 64 | "Name must be 64 characters or less" | +| Unique name (case-insensitive) | "A saved search with this name already exists" | + +### Auto-Apply Default Search + +The default search auto-applies when: + +1. User **navigates** to the Alert Rules page (not on refresh) +2. No search query is present in the URL +3. A default search is configured + +This is tracked via `sessionStorage` to distinguish navigation from refresh. + +### Persistence + +Saved searches are stored using `UserStorage`: + +- **Backend API**: `/apis/userstorage.grafana.app/v0alpha1/namespaces/{namespace}/user-storage` +- **Fallback**: `localStorage` (when user not signed in or API fails) +- **Storage key**: `alerting.savedSearches` + +## Analytics + +The feature tracks the following events via `reportInteraction`: + +| Event | Properties | When | +| ------------------------------------------- | -------------------------- | -------------------- | +| `grafana_alerting_saved_search_save` | `hasDefault`, `totalCount` | Search saved | +| `grafana_alerting_saved_search_apply` | `isDefault` | Search applied | +| `grafana_alerting_saved_search_delete` | - | Search deleted | +| `grafana_alerting_saved_search_rename` | - | Search renamed | +| `grafana_alerting_saved_search_set_default` | `action: 'set' \| 'clear'` | Default changed | +| `grafana_alerting_saved_search_auto_apply` | - | Default auto-applied | + +## Testing + +### Component Tests + +Location: `SavedSearches.test.tsx` + +```bash +yarn test SavedSearches.test.tsx +``` + +Test categories: + +- **Rendering**: Button, dropdown, list sorting, empty state +- **Save functionality**: Validation, errors, success flow +- **Apply functionality**: Click handling, dropdown close +- **Action menu**: Set default, rename, delete options +- **Delete confirmation**: Confirm/cancel flows +- **Keyboard navigation**: Escape key handling +- **Edge cases**: Empty queries, whitespace trimming + +### Hook Tests + +Location: `useSavedSearches.test.tsx` + +```bash +yarn test useSavedSearches.test.tsx +``` + +Test categories: + +- **Initial loading**: Loading state, storage load, empty storage +- **saveSearch**: New search, duplicate detection, analytics +- **renameSearch**: Rename flow, duplicate detection +- **deleteSearch**: Delete flow, analytics +- **setDefaultSearch**: Set/clear default, analytics +- **getAutoApplySearch**: Navigation detection, URL check +- **Error handling**: Storage errors, notifications + +### Mocking UserStorage API with MSW + +The hook and component tests use MSW to mock the UserStorage API endpoints. +The handlers are defined in `mocks/server/handlers/userStorage.ts` and included via `setupMswServer()`: + +```typescript +import { setupMswServer, setAlertingStorageItem, getAlertingStorageItem } from 'app/features/alerting/unified/mockApi'; + +// Set up MSW server with UserStorage handlers (call at module level) +setupMswServer(); + +// In tests, use helper functions to set up storage data: +it('should load saved searches', async () => { + setAlertingStorageItem('savedSearches', JSON.stringify(mockSavedSearches)); + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + expect(result.current.savedSearches).toEqual(mockSavedSearches); +}); + +// Verify persisted data: +it('should save a new search', async () => { + // ... perform save action ... + + const storedData = getAlertingStorageItem('savedSearches'); + expect(storedData).toContain('"name":"New Search"'); +}); +``` + +**Important**: Tests must mock `config.namespace` and `config.bootData.user` before imports, +so that `UserStorage` constructs the correct API URLs: + +```typescript +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + config: { + ...jest.requireActual('@grafana/runtime').config, + namespace: 'default', + bootData: { + ...jest.requireActual('@grafana/runtime').config.bootData, + user: { uid: 'test-user-123', id: 123, isSignedIn: true }, + }, + }, +})); +``` + +### Verifying Notifications in UI + +Tests verify error notifications by rendering the `AppNotificationList` component: + +```typescript +import { AppNotificationList } from 'app/core/components/AppNotifications/AppNotificationList'; +import { getWrapper, screen } from 'test/test-utils'; + +function createWrapper() { + const Wrapper = getWrapper({ renderWithRouter: true }); + return function WrapperWithNotifications({ children }) { + return ( + + + {children} + + ); + }; +} + +// In tests (e.g., malformed JSON triggers error notification): +it('should handle malformed JSON gracefully', async () => { + setAlertingStorageItem('savedSearches', 'not valid json'); + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + expect(await screen.findByText(/failed to load saved searches/i)).toBeInTheDocument(); +}); +``` + +## Accessibility + +- Dropdown has `role="dialog"` for screen readers +- Action menu uses `@grafana/ui` `Dropdown` and `Menu` components +- Keyboard support: + - `Escape`: Close dropdown or cancel current operation + - `Tab`: Navigate through interactive elements + - `Enter`: Confirm inputs + +## File Structure + +``` +public/app/features/alerting/unified/rule-list/filter/ +├── SavedSearches.tsx # Main component +├── SavedSearches.test.tsx # Component tests +├── SavedSearches.README.md # This documentation +├── useSavedSearches.ts # Custom hook with persistence +└── useSavedSearches.test.tsx # Hook tests +``` + +## Dependencies + +- `@grafana/ui`: Button, Dropdown, Menu, Icon, Input, Stack, Box, Spinner, PopupCard +- `@grafana/i18n`: Trans, t (internationalization) +- `@grafana/runtime`: reportInteraction +- `@grafana/runtime/internal`: UserStorage +- `@emotion/css`: Styling via useStyles2 + +## E2E Tests + +Location: `e2e-playwright/alerting-suite/saved-searches.spec.ts` + +```bash +yarn e2e:playwright --grep "saved-searches" +``` + +Test scenarios: + +- Display Saved searches button +- Open/close dropdown +- Empty state +- Save current search (enabled/disabled) +- Create new saved search +- Validation errors (duplicate name) +- Apply saved search +- Rename saved search +- Delete saved search +- Set as default +- Keyboard navigation (Escape to close/cancel) diff --git a/public/app/features/alerting/unified/rule-list/filter/SavedSearches.test.tsx b/public/app/features/alerting/unified/rule-list/filter/SavedSearches.test.tsx new file mode 100644 index 00000000000..7e3604c5eec --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/filter/SavedSearches.test.tsx @@ -0,0 +1,348 @@ +import { render, screen, waitFor } from 'test/test-utils'; +import { byPlaceholderText, byRole, byText } from 'testing-library-selector'; + +import { SavedSearches } from './SavedSearches'; +import { SavedSearch } from './savedSearchesSchema'; + +/** + * UI selectors for SavedSearches component tests. + * Using testing-library-selector for reusable, consistent selectors. + */ +const ui = { + // Main trigger button + savedSearchesButton: byRole('button', { name: /saved searches/i }), + // Dropdown dialog + dropdown: byRole('dialog'), + // Save functionality + saveButton: byRole('button', { name: /save current search/i }), + saveConfirmButton: byRole('button', { name: /save$/i }), + saveInput: byPlaceholderText(/enter a name/i), + // Action buttons + cancelButton: byRole('button', { name: /cancel/i }), + applyButtons: byRole('button', { name: /apply this search/i }), + actionMenuButtons: byRole('button', { name: /actions/i }), + deleteButton: byRole('button', { name: /delete/i }), + // Menu items (using byRole for proper accessibility testing) + setAsDefaultMenuItem: byRole('menuitem', { name: /set as default/i }), + removeDefaultMenuItem: byRole('menuitem', { name: /remove default/i }), + renameMenuItem: byRole('menuitem', { name: /rename/i }), + deleteMenuItem: byRole('menuitem', { name: /^delete$/i }), + // Messages + emptyStateMessage: byText(/no saved searches/i), + nameRequiredError: byText(/name is required/i), + duplicateNameError: byText(/a saved search with this name already exists/i), +}; + +// Mock data is ordered as it will appear after sorting by useSavedSearches: +// default search first, then alphabetically by name +const mockSavedSearches: SavedSearch[] = [ + { + id: '2', + name: 'Default Search', + query: 'label:team=A', + isDefault: true, + createdAt: Date.now() - 2000, + }, + { + id: '3', + name: 'Critical Alerts', + query: 'label:severity=critical state:firing', + isDefault: false, + createdAt: Date.now() - 3000, + }, + { + id: '1', + name: 'My Firing Rules', + query: 'state:firing', + isDefault: false, + createdAt: Date.now() - 1000, + }, +]; + +const defaultProps = { + savedSearches: mockSavedSearches, + currentSearchQuery: '', + onSave: jest.fn(), + onRename: jest.fn(), + onDelete: jest.fn(), + onApply: jest.fn(), + onSetDefault: jest.fn(), +}; + +describe('SavedSearches', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('Displaying saved searches', () => { + it('shows empty state when no saved searches exist', async () => { + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + + expect(ui.emptyStateMessage.get()).toBeInTheDocument(); + }); + + it('displays saved searches with default search marked with star icon', async () => { + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + + // Verify searches are displayed + const applyButtons = await ui.applyButtons.findAll(); + expect(applyButtons).toHaveLength(3); + + // Verify the default search has a star icon + expect(screen.getByText('Default Search')).toBeInTheDocument(); + expect(screen.getByTitle('Default search')).toBeInTheDocument(); + }); + }); + + describe('Saving a search', () => { + it('saves current search with the provided name', async () => { + defaultProps.onSave.mockResolvedValue(undefined); + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + await user.click(await ui.saveButton.find()); + await user.type(await ui.saveInput.find(), 'My New Search'); + await user.click(ui.saveConfirmButton.get()); + + expect(defaultProps.onSave).toHaveBeenCalledWith('My New Search', 'state:pending'); + }); + + it('disables save button when currentSearchQuery is empty', async () => { + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + + // Use findByText + closest because findByRole fails to find disabled Grafana Button + // (due to aria-disabled="false" + disabled="" attribute mismatch in Grafana UI) + const saveButtonText = await screen.findByText(/save current search/i); + // eslint-disable-next-line testing-library/no-node-access + const saveButton = saveButtonText.closest('button'); + expect(saveButton).toBeDisabled(); + }); + + it('shows validation error when name is empty', async () => { + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + await user.click(await ui.saveButton.find()); + await user.click(await ui.saveConfirmButton.find()); + + expect(await ui.nameRequiredError.find()).toBeInTheDocument(); + }); + + it('shows validation error for duplicate name', async () => { + defaultProps.onSave.mockResolvedValue({ + field: 'name', + message: 'A saved search with this name already exists', + }); + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + await user.click(await ui.saveButton.find()); + await user.type(await ui.saveInput.find(), 'My Firing Rules'); + await user.click(ui.saveConfirmButton.get()); + + expect(await ui.duplicateNameError.find()).toBeInTheDocument(); + }); + + it('trims whitespace from search name before saving', async () => { + defaultProps.onSave.mockResolvedValue(undefined); + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + await user.click(await ui.saveButton.find()); + await user.type(await ui.saveInput.find(), ' My Search '); + await user.click(ui.saveConfirmButton.get()); + + expect(defaultProps.onSave).toHaveBeenCalledWith('My Search', 'state:pending'); + }); + + it('cancels save when cancel button is clicked', async () => { + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + await user.click(await ui.saveButton.find()); + await user.click(await ui.cancelButton.find()); + + expect(defaultProps.onSave).not.toHaveBeenCalled(); + expect(ui.saveInput.query()).not.toBeInTheDocument(); + }); + }); + + describe('Applying a search', () => { + it('applies the selected search and closes dropdown', async () => { + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + const applyButtons = await ui.applyButtons.findAll(); + // Click the apply button for "My Firing Rules" (third in list: Default, Critical, My Firing) + await user.click(applyButtons[2]); + + expect(defaultProps.onApply).toHaveBeenCalledWith( + expect.objectContaining({ + id: '1', + name: 'My Firing Rules', + query: 'state:firing', + }) + ); + + await waitFor(() => { + expect(ui.dropdown.query()).not.toBeInTheDocument(); + }); + }); + }); + + describe('Setting default search', () => { + it('sets a search as default', async () => { + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + // Use a non-default search's menu (second item: "Critical Alerts") + const menuButtons = await ui.actionMenuButtons.findAll(); + await user.click(menuButtons[1]); + await user.click(await ui.setAsDefaultMenuItem.find()); + + expect(defaultProps.onSetDefault).toHaveBeenCalledWith('3'); + }); + + it('removes default from a search', async () => { + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + // Default Search is first item (index 0) + const menuButtons = await ui.actionMenuButtons.findAll(); + await user.click(menuButtons[0]); + await user.click(await ui.removeDefaultMenuItem.find()); + + expect(defaultProps.onSetDefault).toHaveBeenCalledWith(null); + }); + }); + + describe('Renaming a search', () => { + it('renames a search successfully', async () => { + defaultProps.onRename.mockResolvedValue(undefined); + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + const menuButtons = await ui.actionMenuButtons.findAll(); + await user.click(menuButtons[0]); + await user.click(await ui.renameMenuItem.find()); + + const input = await screen.findByDisplayValue('Default Search'); + await user.clear(input); + await user.type(input, 'Renamed Search'); + await user.keyboard('{Enter}'); + + expect(defaultProps.onRename).toHaveBeenCalledWith('2', 'Renamed Search'); + }); + + it('shows validation error for duplicate name when renaming', async () => { + defaultProps.onRename.mockResolvedValue({ + field: 'name', + message: 'A saved search with this name already exists', + }); + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + const menuButtons = await ui.actionMenuButtons.findAll(); + await user.click(menuButtons[0]); + await user.click(await ui.renameMenuItem.find()); + + const input = await screen.findByDisplayValue('Default Search'); + await user.clear(input); + await user.type(input, 'My Firing Rules'); + await user.keyboard('{Enter}'); + + await waitFor(() => { + expect(ui.duplicateNameError.get()).toBeInTheDocument(); + }); + }); + }); + + describe('Deleting a search', () => { + it('deletes a search after confirmation', async () => { + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + const menuButtons = await ui.actionMenuButtons.findAll(); + // Delete the first item (Default Search, id: '2') + await user.click(menuButtons[0]); + await user.click(await ui.deleteMenuItem.find()); + + // Confirm delete + const deleteButtons = await ui.deleteButton.findAll(); + await user.click(deleteButtons[deleteButtons.length - 1]); + + expect(defaultProps.onDelete).toHaveBeenCalledWith('2'); + }); + + it('cancels delete when cancel is clicked', async () => { + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + const menuButtons = await ui.actionMenuButtons.findAll(); + await user.click(menuButtons[0]); + await user.click(await ui.deleteMenuItem.find()); + + await user.click(await ui.cancelButton.find()); + + expect(defaultProps.onDelete).not.toHaveBeenCalled(); + expect(screen.getByText('Default Search')).toBeInTheDocument(); + }); + }); + + describe('Keyboard navigation', () => { + it('closes dropdown when Escape is pressed', async () => { + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + expect(await ui.dropdown.find()).toBeInTheDocument(); + + await user.keyboard('{Escape}'); + + await waitFor(() => { + expect(ui.dropdown.query()).not.toBeInTheDocument(); + }); + }); + + it('cancels save mode when Escape is pressed without closing dropdown', async () => { + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + await user.click(await ui.saveButton.find()); + expect(await ui.saveInput.find()).toBeInTheDocument(); + + await user.keyboard('{Escape}'); + + await waitFor(() => { + expect(ui.saveInput.query()).not.toBeInTheDocument(); + }); + // Dropdown should still be open + expect(ui.dropdown.get()).toBeInTheDocument(); + }); + }); + + describe('Edge cases', () => { + it('handles empty search query display gracefully', async () => { + const searchesWithEmptyQuery: SavedSearch[] = [ + { + id: '1', + name: 'Empty Search', + query: '', + isDefault: false, + createdAt: Date.now(), + }, + ]; + + const { user } = render(); + + await user.click(ui.savedSearchesButton.get()); + + expect(await screen.findByText('Empty Search')).toBeInTheDocument(); + }); + }); +}); diff --git a/public/app/features/alerting/unified/rule-list/filter/SavedSearches.tsx b/public/app/features/alerting/unified/rule-list/filter/SavedSearches.tsx new file mode 100644 index 00000000000..0d4391642f0 --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/filter/SavedSearches.tsx @@ -0,0 +1,556 @@ +/** + * SavedSearches Component + * + * Allows users to save, manage, and quickly apply search queries on the Alert Rules page. + * + * ## Features + * - Save current search query with a custom name + * - Mark one search as "default" (auto-applied on navigation) + * - Rename, delete, and apply saved searches + * - Alphabetical sorting with default search pinned first + * + * ## Props + * @param savedSearches - Array of saved search objects + * @param currentSearchQuery - The current search query string from the filter state + * @param onSave - Callback to save a new search. Throws ValidationError on failure. + * @param onRename - Callback to rename an existing search. Throws ValidationError on failure. + * @param onDelete - Callback to delete a search + * @param onApply - Callback when a saved search is applied + * @param onSetDefault - Callback to set/unset default search (pass null to unset) + * @param disabled - Disables all interactions + * @param className - Additional CSS class name + * + * ## Internal States + * - Dropdown open/closed + * - Save mode (inputting new search name) + * - Rename mode (editing existing search name, by item ID) + * - Delete confirm mode (confirming deletion, by item ID) + * + * ## Accessibility + * - Uses role="dialog" for the dropdown panel + * - Basic keyboard navigation (Escape to close, Tab to navigate) + * - Focus moves to "Save current search" button when dropdown opens + * + * @example + * ```tsx + * + * ``` + */ + +import { css } from '@emotion/css'; +import { useCallback, useEffect, useReducer, useRef } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; +import { Box, Button, Spinner, Stack, Text, useStyles2 } from '@grafana/ui'; + +import { PopupCard } from '../../components/HoverCard'; + +import { InlineSaveInput } from './InlineSaveInput'; +import { SavedSearchItem } from './SavedSearchItem'; +import { SavedSearch } from './savedSearchesSchema'; + +// ============================================================================ +// Types +// ============================================================================ + +export interface SavedSearchesProps { + /** Array of saved search objects */ + savedSearches: SavedSearch[]; + /** The current search query string from the filter state */ + currentSearchQuery: string; + /** Callback to save a new search. Throws ValidationError on failure. */ + onSave: (name: string, query: string) => Promise; + /** Callback to rename an existing search. Throws ValidationError on failure. */ + onRename: (id: string, newName: string) => Promise; + /** Callback to delete a search */ + onDelete: (id: string) => Promise; + /** Callback when a saved search is applied */ + onApply: (search: SavedSearch) => void; + /** Callback to set/unset default search. Pass null to remove default. */ + onSetDefault: (id: string | null) => Promise; + /** Whether saved searches are still loading from storage */ + isLoading?: boolean; + /** Additional CSS class name */ + className?: string; +} + +// ============================================================================ +// State Management (Reducer) +// ============================================================================ + +// Active action type - represents the current action in progress +type ActiveAction = 'idle' | { type: 'saving' } | { type: 'renaming'; id: string } | { type: 'deleting'; id: string }; + +// Component state +interface DropdownState { + isOpen: boolean; + activeAction: ActiveAction; +} + +// Action types for the reducer +type DropdownAction = + | { type: 'OPEN' } + | { type: 'CLOSE' } + | { type: 'SET_VISIBLE'; visible: boolean } + | { type: 'START_SAVE' } + | { type: 'START_RENAME'; id: string } + | { type: 'START_DELETE'; id: string } + | { type: 'CANCEL_ACTION' } + | { type: 'COMPLETE_ACTION' } + | { type: 'APPLY_AND_CLOSE' }; + +const initialState: DropdownState = { + isOpen: false, + activeAction: 'idle', +}; + +/** + * Reducer for managing dropdown state and active actions. + * Centralizes all state transitions for easier reasoning and testing. + */ +function dropdownReducer(state: DropdownState, action: DropdownAction): DropdownState { + switch (action.type) { + case 'OPEN': + return { ...state, isOpen: true }; + + case 'CLOSE': + // Reset action when closing + return { isOpen: false, activeAction: 'idle' }; + + case 'SET_VISIBLE': + // When visibility changes, reset action if closing + return action.visible ? { ...state, isOpen: true } : { isOpen: false, activeAction: 'idle' }; + + case 'START_SAVE': + // Only start save if no action is active + return state.activeAction === 'idle' ? { ...state, activeAction: { type: 'saving' } } : state; + + case 'START_RENAME': + // Only start rename if no action is active + return state.activeAction === 'idle' ? { ...state, activeAction: { type: 'renaming', id: action.id } } : state; + + case 'START_DELETE': + // Only start delete if no action is active + return state.activeAction === 'idle' ? { ...state, activeAction: { type: 'deleting', id: action.id } } : state; + + case 'CANCEL_ACTION': + case 'COMPLETE_ACTION': + // Return to idle state + return { ...state, activeAction: 'idle' }; + + case 'APPLY_AND_CLOSE': + // Only apply if no action is active, then close + return state.activeAction === 'idle' ? { isOpen: false, activeAction: 'idle' } : state; + + default: + return state; + } +} + +// ============================================================================ +// Main Component +// ============================================================================ + +export function SavedSearches({ + savedSearches, + currentSearchQuery, + onSave, + onRename, + onDelete, + onApply, + onSetDefault, + isLoading = false, + className, +}: SavedSearchesProps) { + const styles = useStyles2(getStyles); + + // Centralized state management via reducer + const [state, dispatch] = useReducer(dropdownReducer, initialState); + const { isOpen, activeAction } = state; + + // Refs + const saveButtonRef = useRef(null); + const dialogRef = useRef(null); + + // Focus dialog when dropdown opens to enable keyboard navigation + useEffect(() => { + if (isOpen && activeAction === 'idle') { + // Small delay to ensure the dropdown is rendered + const timer = setTimeout(() => { + // Focus the dialog to capture keyboard events (like Escape) + // We focus the dialog instead of the save button because the button may be disabled + dialogRef.current?.focus(); + }, 50); + return () => clearTimeout(timer); + } + return undefined; + }, [isOpen, activeAction]); + + // Handle click outside to close dropdown - excludes portal elements (like action menu) + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (isOpen && dialogRef.current && event.target instanceof Node && !dialogRef.current.contains(event.target)) { + // Check if click is on a portal element (action menu dropdown) + if (event.target instanceof Element) { + const isPortalClick = + event.target.closest('[data-popper-placement]') || event.target.closest('[role="menu"]'); + + if (!isPortalClick) { + dispatch({ type: 'CLOSE' }); + } + } else { + dispatch({ type: 'CLOSE' }); + } + } + }; + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isOpen]); + + // Handle Escape key: cancel active action first, or close dropdown if no action is active + useEffect(() => { + const handleEscapeKey = (event: KeyboardEvent) => { + if (event.key === 'Escape' && isOpen) { + if (activeAction !== 'idle') { + dispatch({ type: 'CANCEL_ACTION' }); + } else { + dispatch({ type: 'CLOSE' }); + } + } + }; + + document.addEventListener('keydown', handleEscapeKey); + return () => document.removeEventListener('keydown', handleEscapeKey); + }, [isOpen, activeAction]); + + // Handlers + const handleToggle = useCallback(() => { + dispatch({ type: isOpen ? 'CLOSE' : 'OPEN' }); + }, [isOpen]); + + const handleClose = useCallback(() => { + dispatch({ type: 'CLOSE' }); + }, []); + + const handleStartSave = useCallback(() => { + dispatch({ type: 'START_SAVE' }); + }, []); + + const handleCancelSave = useCallback(() => { + dispatch({ type: 'CANCEL_ACTION' }); + }, []); + + const handleSaveComplete = useCallback( + async (name: string): Promise => { + await onSave(name, currentSearchQuery); + dispatch({ type: 'COMPLETE_ACTION' }); + }, + [onSave, currentSearchQuery] + ); + + const handleStartRename = useCallback((id: string) => { + dispatch({ type: 'START_RENAME', id }); + }, []); + + const handleCancelRename = useCallback(() => { + dispatch({ type: 'CANCEL_ACTION' }); + }, []); + + const handleRenameComplete = useCallback( + async (id: string, newName: string): Promise => { + await onRename(id, newName); + dispatch({ type: 'COMPLETE_ACTION' }); + }, + [onRename] + ); + + const handleStartDelete = useCallback((id: string) => { + dispatch({ type: 'START_DELETE', id }); + }, []); + + const handleCancelDelete = useCallback(() => { + dispatch({ type: 'CANCEL_ACTION' }); + }, []); + + const handleDeleteConfirm = useCallback( + async (id: string) => { + await onDelete(id); + dispatch({ type: 'COMPLETE_ACTION' }); + }, + [onDelete] + ); + + const handleApply = useCallback( + (search: SavedSearch) => { + // Only allow apply when no action is active (handled by reducer) + if (activeAction !== 'idle') { + return; + } + onApply(search); + dispatch({ type: 'APPLY_AND_CLOSE' }); + }, + [onApply, activeAction] + ); + + const handleSetDefault = useCallback( + async (id: string | null) => { + // Only allow set default when no action is active + if (activeAction !== 'idle') { + return; + } + await onSetDefault(id); + }, + [onSetDefault, activeAction] + ); + + const buttonLabel = t('alerting.saved-searches.button-label', 'Saved searches'); + const hasSearches = savedSearches.length > 0; + const canSave = currentSearchQuery.trim().length > 0; + + const content = ( +
+ +
+ ); + + return ( + + + + ); +} + +// ============================================================================ +// List Mode (shows saved searches or empty state) +// ============================================================================ + +interface ListModeProps { + searches: SavedSearch[]; + hasSearches: boolean; + canSave: boolean; + activeAction: ActiveAction; + saveButtonRef: React.RefObject; + isLoading: boolean; + onStartSave: () => void; + /** Callback to complete save. Throws ValidationError on validation failure. */ + onSaveComplete: (name: string) => Promise; + onCancelSave: () => void; + onApply: (search: SavedSearch) => void; + onStartRename: (id: string) => void; + onCancelRename: () => void; + /** Callback to complete rename. Throws ValidationError on validation failure. */ + onRenameComplete: (id: string, newName: string) => Promise; + onStartDelete: (id: string) => void; + onCancelDelete: () => void; + onDeleteConfirm: (id: string) => Promise; + onSetDefault: (id: string | null) => Promise; + savedSearches: SavedSearch[]; + /** Portal root for action menus - renders inside the dropdown to prevent useDismiss issues */ + menuPortalRoot: HTMLElement | null; +} + +function ListMode({ + searches, + hasSearches, + canSave, + activeAction, + saveButtonRef, + isLoading, + onStartSave, + onSaveComplete, + onCancelSave, + onApply, + onStartRename, + onCancelRename, + onRenameComplete, + onStartDelete, + onCancelDelete, + onDeleteConfirm, + onSetDefault, + savedSearches, + menuPortalRoot, +}: ListModeProps) { + const styles = useStyles2(getStyles); + + // Derived states from activeAction + const isSaveMode = typeof activeAction === 'object' && activeAction.type === 'saving'; + const isActionActive = activeAction !== 'idle'; + + // Show loading state + if (isLoading) { + return ( + + + + ); + } + + return ( + + {/* Save current search - button or inline input */} + {/* Stop propagation to prevent Dropdown from closing when interacting with save form */} + {isSaveMode ? ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions +
e.stopPropagation()}> + +
+ ) : ( + + + + )} + + {/* Empty state or list */} + {!hasSearches ? ( + + ) : ( + +
+ {searches.map((search) => { + const isRenaming = + typeof activeAction === 'object' && activeAction.type === 'renaming' && activeAction.id === search.id; + const isDeleting = + typeof activeAction === 'object' && activeAction.type === 'deleting' && activeAction.id === search.id; + // Item is disabled if any action is active and this item is not the one being acted upon + const isItemDisabled = isActionActive && !isRenaming && !isDeleting; + + return ( + onApply(search)} + onStartRename={() => onStartRename(search.id)} + onCancelRename={onCancelRename} + onRenameComplete={(newName) => onRenameComplete(search.id, newName)} + onStartDelete={() => onStartDelete(search.id)} + onCancelDelete={onCancelDelete} + onDeleteConfirm={() => onDeleteConfirm(search.id)} + onSetDefault={() => onSetDefault(search.isDefault ? null : search.id)} + savedSearches={savedSearches} + menuPortalRoot={menuPortalRoot} + /> + ); + })} +
+
+ )} +
+ ); +} + +// ============================================================================ +// Empty State +// ============================================================================ + +function EmptyState() { + return ( + + + No saved searches yet + + + ); +} + +// ============================================================================ +// Styles +// ============================================================================ + +function getStyles(theme: GrafanaTheme2) { + return { + dropdown: css({ + width: '320px', + padding: theme.spacing(0.5), + }), + list: css({ + maxHeight: '300px', + overflowY: 'auto', + }), + item: css({ + padding: theme.spacing(0.5), + borderRadius: theme.shape.radius.default, + '&:hover': { + backgroundColor: theme.colors.action.hover, + }, + }), + }; +} diff --git a/public/app/features/alerting/unified/rule-list/filter/savedSearchesSchema.ts b/public/app/features/alerting/unified/rule-list/filter/savedSearchesSchema.ts new file mode 100644 index 00000000000..807ce421957 --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/filter/savedSearchesSchema.ts @@ -0,0 +1,87 @@ +/** + * Shared types and utilities for SavedSearches feature. + * + * This module is extracted to avoid circular dependencies between: + * - SavedSearches.tsx (main component) + * - InlineSaveInput.tsx, InlineRenameInput.tsx, SavedSearchItem.tsx (sub-components) + * - useSavedSearches.ts (hook) + */ + +import z from 'zod'; + +import { t } from '@grafana/i18n'; + +// ============================================================================ +// Schemas +// ============================================================================ + +/** + * Zod schema for validating a saved search object. + * Used to validate data loaded from storage. + */ +export const savedSearchSchema = z.object({ + id: z.string(), + name: z.string(), + isDefault: z.boolean(), + query: z.string(), + createdAt: z.number().optional(), +}); + +/** + * Zod schema for validating an array of saved searches. + */ +export const savedSearchesArraySchema = z.array(savedSearchSchema); + +// ============================================================================ +// Types +// ============================================================================ + +export type SavedSearch = z.infer; + +export interface ValidationError { + field: 'name'; + message: string; +} + +/** + * Type guard to check if an error is a ValidationError. + */ +export function isValidationError(error: unknown): error is ValidationError { + return Boolean( + error && + typeof error === 'object' && + 'field' in error && + 'message' in error && + error.field === 'name' && + typeof error.message === 'string' + ); +} + +// ============================================================================ +// Validation Utilities +// ============================================================================ + +/** + * Validates a saved search name. + * @param name - The name to validate + * @param savedSearches - Existing saved searches for uniqueness check + * @param excludeId - Optional ID to exclude from uniqueness check (for rename) + * @returns Error message string or null if valid + */ +export function validateSearchName(name: string, savedSearches: SavedSearch[], excludeId?: string): string | null { + const trimmed = name.trim(); + + if (!trimmed) { + return t('alerting.saved-searches.error-name-required', 'Name is required'); + } + + const isDuplicate = savedSearches.some( + (s) => (excludeId ? s.id !== excludeId : true) && s.name.toLowerCase() === trimmed.toLowerCase() + ); + + if (isDuplicate) { + return t('alerting.saved-searches.error-name-duplicate', 'A saved search with this name already exists'); + } + + return null; +} diff --git a/public/app/features/alerting/unified/rule-list/filter/useApplyDefaultSearch.ts b/public/app/features/alerting/unified/rule-list/filter/useApplyDefaultSearch.ts new file mode 100644 index 00000000000..380702fed16 --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/filter/useApplyDefaultSearch.ts @@ -0,0 +1,86 @@ +import { useEffect } from 'react'; + +import { shouldUseSavedSearches } from '../../featureToggles'; +import { useAsync } from '../../hooks/useAsync'; +import { useRulesFilter } from '../../hooks/useFilteredRules'; +import { getSearchFilterFromQuery } from '../../search/rulesSearchParser'; + +import { loadDefaultSavedSearch, trackSavedSearchAutoApply } from './useSavedSearches'; + +/** + * Session storage key to track if user has visited this page in current session. + * Used to determine if we should auto-apply the default saved search. + */ +const SESSION_VISITED_KEY = 'grafana.alerting.ruleList.visited'; + +/** + * Hook that automatically applies the default saved search on first visit to the page. + * + * This hook: + * - Checks if saved searches feature is enabled + * - Detects if this is the first visit in the current session + * - Loads and applies the default saved search if one exists + * - Cleans up session storage on unmount + * + * @returns Object with isApplying boolean indicating if the default search is being loaded/applied + */ +export function useApplyDefaultSearch(): { isApplying: boolean } { + const savedSearchesEnabled = shouldUseSavedSearches(); + const { updateFilters, hasActiveFilters } = useRulesFilter(); + + // Use the internal useAsync hook which doesn't auto-execute + const [{ execute }, state] = useAsync(async () => { + const defaultSearch = await loadDefaultSavedSearch(); + if (defaultSearch) { + updateFilters(getSearchFilterFromQuery(defaultSearch.query)); + trackSavedSearchAutoApply(); + } + }); + + // Clear session storage on unmount + useEffect(() => { + return () => { + clearSessionVisitedFlag(); + }; + }, []); + + const isFirstVisit = isFirstVisitInSession(); + const shouldLoadDefault = savedSearchesEnabled && !hasActiveFilters && isFirstVisit; + + // Mark as visited on first visit, regardless of whether we load defaults + if (isFirstVisit && state.status === 'not-executed') { + markAsVisited(); + + // Execute only if we should load default + if (shouldLoadDefault) { + execute(); + } + } + + return { isApplying: state.status === 'loading' }; +} + +/** + * Check if this is a fresh navigation to the page (not a refresh or in-page URL change). + * Uses session storage which persists across refreshes but clears when tab is closed. + * + * @returns true if this is the first visit to the page in this session + */ +function isFirstVisitInSession(): boolean { + return !sessionStorage.getItem(SESSION_VISITED_KEY); +} + +/** + * Mark the page as visited in the current session. + */ +function markAsVisited(): void { + sessionStorage.setItem(SESSION_VISITED_KEY, 'true'); +} + +/** + * Clear the session visited flag. Call this when component unmounts + * so the next navigation to this page is detected as a fresh visit. + */ +function clearSessionVisitedFlag(): void { + sessionStorage.removeItem(SESSION_VISITED_KEY); +} diff --git a/public/app/features/alerting/unified/rule-list/filter/useSavedSearches.test.tsx b/public/app/features/alerting/unified/rule-list/filter/useSavedSearches.test.tsx new file mode 100644 index 00000000000..4ea83f1e38a --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/filter/useSavedSearches.test.tsx @@ -0,0 +1,400 @@ +import { PropsWithChildren } from 'react'; +import { act, getWrapper, renderHook, screen, waitFor } from 'test/test-utils'; + +import * as runtime from '@grafana/runtime'; +import { AppNotificationList } from 'app/core/components/AppNotifications/AppNotificationList'; +import { setupMswServer } from 'app/features/alerting/unified/mockApi'; + +// Create mock UserStorage instance that can be configured per test +const mockUserStorage = { + getItem: jest.fn(), + setItem: jest.fn(), +}; + +// Mock UserStorage class from @grafana/runtime/internal +// This prevents the module-level instance from caching state across tests +jest.mock('@grafana/runtime/internal', () => ({ + ...jest.requireActual('@grafana/runtime/internal'), + UserStorage: jest.fn().mockImplementation(() => mockUserStorage), +})); + +// Mock config BEFORE any imports that use it (jest.mock is hoisted) +// This ensures config.namespace and config.bootData.user are set when UserStorage module loads +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + reportInteraction: jest.fn(), + config: { + ...jest.requireActual('@grafana/runtime').config, + namespace: 'default', + bootData: { + ...jest.requireActual('@grafana/runtime').config.bootData, + navTree: [], + user: { + uid: 'test-user-123', + id: 123, + isSignedIn: true, + }, + }, + }, +})); + +import { trackSavedSearchApplied, useSavedSearches } from './useSavedSearches'; + +// Set up MSW server for other handlers (not UserStorage - that's mocked directly) +setupMswServer(); + +// Mock data is ordered as it will appear after sorting by useSavedSearches: +// default search first, then alphabetically by name +const mockSavedSearches = [ + { + id: '2', + name: 'Default Search', + query: 'label:team=A', + isDefault: true, + createdAt: Date.now() - 2000, + }, + { + id: '1', + name: 'Test Search 1', + query: 'state:firing', + isDefault: false, + createdAt: Date.now() - 1000, + }, +]; + +// Wrapper that includes AppNotificationList to verify UI notifications +function createWrapper() { + const Wrapper = getWrapper({ renderWithRouter: true }); + return function WrapperWithNotifications({ children }: PropsWithChildren) { + return ( + + + {children} + + ); + }; +} + +describe('useSavedSearches', () => { + beforeEach(() => { + jest.clearAllMocks(); + sessionStorage.clear(); + localStorage.clear(); + // Reset mock UserStorage to default behavior (empty storage) + mockUserStorage.getItem.mockResolvedValue(null); + mockUserStorage.setItem.mockResolvedValue(undefined); + }); + + describe('Initial loading', () => { + it('should load saved searches from UserStorage', async () => { + mockUserStorage.getItem.mockResolvedValue(JSON.stringify(mockSavedSearches)); + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + expect(result.current.savedSearches).toEqual(mockSavedSearches); + }); + + it('should handle empty storage gracefully', async () => { + // Storage is empty by default after resetUserStorage() in afterEach + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + expect(result.current.savedSearches).toEqual([]); + }); + + it('should handle 404 (no stored data) gracefully', async () => { + // When no data exists, UserStorage returns 404, which is handled as "not found" + // The hook should return an empty array without error + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + // UserStorage handles 404 gracefully - returns empty storage + expect(result.current.savedSearches).toEqual([]); + }); + + it('should filter out invalid saved search entries', async () => { + jest.spyOn(console, 'warn').mockImplementation(); + const mixedData = [ + mockSavedSearches[0], // Valid + { id: '3', name: 'Invalid', query: 123, isDefault: false }, // Invalid query type + { id: '4', name: null, query: 'valid', isDefault: false }, // Invalid name type + mockSavedSearches[1], // Valid + ]; + mockUserStorage.getItem.mockResolvedValue(JSON.stringify(mixedData)); + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + // Invalid entries are filtered out, valid ones are preserved + expect(result.current.savedSearches).toHaveLength(2); + expect(result.current.savedSearches[0].name).toBe('Default Search'); + expect(result.current.savedSearches[1].name).toBe('Test Search 1'); + }); + + it('should handle malformed JSON gracefully', async () => { + jest.spyOn(console, 'error').mockImplementation(); + mockUserStorage.getItem.mockResolvedValue('not valid json'); + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + // Verify error notification appears in the UI + expect(await screen.findByText(/failed to load saved searches/i)).toBeInTheDocument(); + expect(result.current.savedSearches).toEqual([]); + }); + }); + + describe('saveSearch', () => { + it('should save a new search', async () => { + // Start with empty storage (default mock behavior) + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + await act(async () => { + await result.current.saveSearch('New Search', 'state:pending'); + }); + + expect(result.current.savedSearches).toHaveLength(1); + expect(result.current.savedSearches[0].name).toBe('New Search'); + expect(result.current.savedSearches[0].query).toBe('state:pending'); + + // Verify data was persisted via UserStorage.setItem + expect(mockUserStorage.setItem).toHaveBeenCalledWith( + 'savedSearches', + expect.stringContaining('"name":"New Search"') + ); + }); + + it('should throw validation error for duplicate name (case-insensitive)', async () => { + mockUserStorage.getItem.mockResolvedValue(JSON.stringify(mockSavedSearches)); + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + await act(async () => { + await expect(result.current.saveSearch('TEST SEARCH 1', 'state:pending')).rejects.toEqual({ + field: 'name', + message: expect.stringContaining('already exists'), + }); + }); + }); + + it('should track analytics on save', async () => { + // Start with empty storage + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + await act(async () => { + await result.current.saveSearch('New Search', 'state:pending'); + }); + + expect(runtime.reportInteraction).toHaveBeenCalledWith( + 'grafana_alerting_saved_search_save', + expect.objectContaining({ + hasDefault: false, + totalCount: 1, + }) + ); + }); + }); + + describe('renameSearch', () => { + it('should rename an existing search', async () => { + mockUserStorage.getItem.mockResolvedValue(JSON.stringify(mockSavedSearches)); + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + await act(async () => { + await result.current.renameSearch('1', 'Renamed Search'); + }); + + expect(result.current.savedSearches.find((s) => s.id === '1')?.name).toBe('Renamed Search'); + + // Verify data was persisted via UserStorage.setItem + expect(mockUserStorage.setItem).toHaveBeenCalledWith( + 'savedSearches', + expect.stringContaining('"name":"Renamed Search"') + ); + }); + + it('should throw validation error for duplicate name on rename', async () => { + mockUserStorage.getItem.mockResolvedValue(JSON.stringify(mockSavedSearches)); + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + await act(async () => { + await expect(result.current.renameSearch('1', 'Default Search')).rejects.toEqual({ + field: 'name', + message: expect.stringContaining('already exists'), + }); + }); + }); + }); + + describe('deleteSearch', () => { + it('should delete a search', async () => { + mockUserStorage.getItem.mockResolvedValue(JSON.stringify(mockSavedSearches)); + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + expect(result.current.savedSearches).toHaveLength(2); + + await act(async () => { + await result.current.deleteSearch('1'); + }); + + expect(result.current.savedSearches).toHaveLength(1); + expect(result.current.savedSearches.find((s) => s.id === '1')).toBeUndefined(); + }); + + it('should track analytics on delete', async () => { + mockUserStorage.getItem.mockResolvedValue(JSON.stringify(mockSavedSearches)); + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + await act(async () => { + await result.current.deleteSearch('1'); + }); + + expect(runtime.reportInteraction).toHaveBeenCalledWith('grafana_alerting_saved_search_delete'); + }); + }); + + describe('setDefaultSearch', () => { + it('should set a search as default', async () => { + mockUserStorage.getItem.mockResolvedValue(JSON.stringify(mockSavedSearches)); + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + await act(async () => { + await result.current.setDefaultSearch('1'); + }); + + expect(result.current.savedSearches.find((s) => s.id === '1')?.isDefault).toBe(true); + expect(result.current.savedSearches.find((s) => s.id === '2')?.isDefault).toBe(false); + }); + + it('should clear default when null is passed', async () => { + mockUserStorage.getItem.mockResolvedValue(JSON.stringify(mockSavedSearches)); + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + await act(async () => { + await result.current.setDefaultSearch(null); + }); + + expect(result.current.savedSearches.every((s) => !s.isDefault)).toBe(true); + }); + + it('should track analytics with correct action', async () => { + mockUserStorage.getItem.mockResolvedValue(JSON.stringify(mockSavedSearches)); + + const { result } = renderHook(() => useSavedSearches(), { wrapper: createWrapper() }); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + }); + + await act(async () => { + await result.current.setDefaultSearch('1'); + }); + + expect(runtime.reportInteraction).toHaveBeenCalledWith('grafana_alerting_saved_search_set_default', { + action: 'set', + }); + + jest.clearAllMocks(); + + await act(async () => { + await result.current.setDefaultSearch(null); + }); + + expect(runtime.reportInteraction).toHaveBeenCalledWith('grafana_alerting_saved_search_set_default', { + action: 'clear', + }); + }); + }); +}); + +describe('trackSavedSearchApplied', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should track with isDefault true for default searches', () => { + trackSavedSearchApplied({ + id: '1', + name: 'Default', + query: 'state:firing', + isDefault: true, + createdAt: Date.now(), + }); + + expect(runtime.reportInteraction).toHaveBeenCalledWith('grafana_alerting_saved_search_apply', { isDefault: true }); + }); + + it('should track with isDefault false for non-default searches', () => { + trackSavedSearchApplied({ + id: '1', + name: 'Regular', + query: 'state:firing', + isDefault: false, + createdAt: Date.now(), + }); + + expect(runtime.reportInteraction).toHaveBeenCalledWith('grafana_alerting_saved_search_apply', { isDefault: false }); + }); +}); diff --git a/public/app/features/alerting/unified/rule-list/filter/useSavedSearches.ts b/public/app/features/alerting/unified/rule-list/filter/useSavedSearches.ts new file mode 100644 index 00000000000..5c81ce990b3 --- /dev/null +++ b/public/app/features/alerting/unified/rule-list/filter/useSavedSearches.ts @@ -0,0 +1,353 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { v4 as uuidv4 } from 'uuid'; + +import { t } from '@grafana/i18n'; +import { reportInteraction } from '@grafana/runtime'; +import { UserStorage } from '@grafana/runtime/internal'; + +import { useAppNotification } from '../../../../../core/copy/appNotification'; +import { logError, logWarning } from '../../Analytics'; +import { isLoading as isLoadingState, isUninitialized, useAsync } from '../../hooks/useAsync'; + +import { SavedSearch, savedSearchSchema, savedSearchesArraySchema, validateSearchName } from './savedSearchesSchema'; + +/** + * Storage key for saved searches in UserStorage. + */ +const SAVED_SEARCHES_STORAGE_KEY = 'savedSearches'; + +/** + * UserStorage instance for saved searches. + * Uses 'alerting' as the service namespace. + */ +const userStorage = new UserStorage('alerting'); + +/** + * Analytics tracking functions for saved search actions. + */ +function trackSavedSearchSave(props: { hasDefault: boolean; totalCount: number }) { + reportInteraction('grafana_alerting_saved_search_save', props); +} + +function trackSavedSearchApply(props: { isDefault: boolean }) { + reportInteraction('grafana_alerting_saved_search_apply', props); +} + +function trackSavedSearchDelete() { + reportInteraction('grafana_alerting_saved_search_delete'); +} + +function trackSavedSearchRename() { + reportInteraction('grafana_alerting_saved_search_rename'); +} + +function trackSavedSearchSetDefault(props: { action: 'set' | 'clear' }) { + reportInteraction('grafana_alerting_saved_search_set_default', props); +} + +export function trackSavedSearchAutoApply() { + reportInteraction('grafana_alerting_saved_search_auto_apply'); +} + +/** + * Validates and parses an array of saved searches using zod schema. + * Returns valid entries and logs warnings for invalid data. + */ +function validateSavedSearches(data: unknown): SavedSearch[] { + const result = savedSearchesArraySchema.safeParse(data); + + if (result.success) { + return result.data; + } + + // If the whole array failed, try to salvage individual valid entries + if (!Array.isArray(data)) { + logWarning('Saved searches data is not an array, returning empty array'); + return []; + } + + logWarning('Saved searches validation failed, filtering invalid entries', { + issues: JSON.stringify(result.error.issues), + }); + + const validEntries: SavedSearch[] = []; + for (const item of data) { + const itemResult = savedSearchSchema.safeParse(item); + if (itemResult.success) { + validEntries.push(itemResult.data); + } + } + return validEntries; +} + +/** + * Sorts saved searches: default search first, then others alphabetically. + * @param searches - Array of saved searches to sort + * @returns Sorted array with default first, then alphabetically by name + */ +function sortSavedSearches(searches: SavedSearch[]): SavedSearch[] { + const defaultSearch = searches.find((s) => s.isDefault); + const collator = new Intl.Collator(undefined, { sensitivity: 'base' }); + const others = searches.filter((s) => !s.isDefault).sort((a, b) => collator.compare(a.name, b.name)); + + return defaultSearch ? [defaultSearch, ...others] : others; +} + +/** + * Loads saved searches from UserStorage and validates the data. + * @returns Promise resolving to an array of valid saved searches + */ +async function loadSavedSearchesFromStorage(): Promise { + const stored = await userStorage.getItem(SAVED_SEARCHES_STORAGE_KEY); + if (!stored) { + return []; + } + + const parsed = JSON.parse(stored); + return validateSavedSearches(parsed); +} + +export async function loadDefaultSavedSearch(): Promise { + const savedSearches = await loadSavedSearchesFromStorage(); + return savedSearches.find((s) => s.isDefault) ?? null; +} + +/** + * Result of the useSavedSearches hook. + */ +export interface UseSavedSearchesResult { + /** List of saved searches */ + savedSearches: SavedSearch[]; + /** Whether the initial load from storage is complete */ + isLoading: boolean; + /** + * Save a new search with the given name and query. + * @param name - The display name for the saved search + * @param query - The search query string + * @throws ValidationError if name is not unique + */ + saveSearch: (name: string, query: string) => Promise; + /** + * Rename an existing saved search. + * @param id - The ID of the search to rename + * @param newName - The new display name + * @throws ValidationError if name is not unique + */ + renameSearch: (id: string, newName: string) => Promise; + /** + * Delete a saved search by ID. + * @param id - The ID of the search to delete + */ + deleteSearch: (id: string) => Promise; + /** + * Set or clear the default search. + * @param id - The ID to set as default, or null to clear + */ + setDefaultSearch: (id: string | null) => Promise; +} + +/** + * Hook for managing saved searches with UserStorage persistence. + * + * Features: + * - Persists saved searches to UserStorage (syncs across devices) + * - Validates data schema on load (filters invalid entries) + * - Validates name uniqueness (case-insensitive) + * - Tracks analytics for all actions + * - Shows error notifications on storage failures + * - Provides auto-apply logic for default search on navigation + * - Per-user session tracking to handle logout/login scenarios + * + * @example + * ```tsx + * const { savedSearches, saveSearch, isLoading } = useSavedSearches(); + * + * // Save current search + * const error = await saveSearch('My Search', currentQuery); + * if (error) { + * // Handle validation error + * } + * + * // Auto-apply default search on mount + * useEffect(() => { + * const defaultSearch = getAutoApplySearch(); + * if (defaultSearch) { + * applySearch(defaultSearch); + * } + * }, []); + * ``` + */ +export function useSavedSearches(): UseSavedSearchesResult { + const [savedSearches, setSavedSearches] = useState([]); + const notifyApp = useAppNotification(); + + // Track whether we've already loaded to prevent double-loading + const hasLoadedRef = useRef(false); + + // Use useAsync for loading state management + const [{ execute: executeLoad }, loadState] = useAsync(loadSavedSearchesFromStorage, []); + const isLoading = isLoadingState(loadState) || isUninitialized(loadState); + + // Load saved searches from storage on mount + useEffect(() => { + if (hasLoadedRef.current) { + return; + } + hasLoadedRef.current = true; + + // Load from UserStorage using async/await pattern + const loadSearches = async () => { + try { + const validated = await executeLoad(); + setSavedSearches(validated); + } catch (error) { + logError(error instanceof Error ? error : new Error('Failed to load saved searches from storage'), { + context: 'useSavedSearches.loadSearches', + }); + notifyApp.error( + t('alerting.saved-searches.error-load-title', 'Failed to load saved searches'), + t( + 'alerting.saved-searches.error-load-description', + 'Your saved searches could not be loaded. Please try refreshing the page.' + ) + ); + } + }; + + loadSearches(); + }, [executeLoad, notifyApp]); + + /** + * Persist saved searches to UserStorage. + */ + const persistSearches = useCallback( + async (searches: SavedSearch[]): Promise => { + try { + await userStorage.setItem(SAVED_SEARCHES_STORAGE_KEY, JSON.stringify(searches)); + } catch (error) { + logError(error instanceof Error ? error : new Error('Failed to save searches'), { + context: 'useSavedSearches.persistSearches', + }); + notifyApp.error( + t('alerting.saved-searches.error-save-title', 'Failed to save'), + t('alerting.saved-searches.error-save-description', 'Your changes could not be saved. Please try again.') + ); + throw error; + } + }, + [notifyApp] + ); + + /** + * Save a new search with the given name and query. + * @throws ValidationError if the name is not unique + */ + const saveSearch = useCallback( + async (name: string, query: string): Promise => { + // Validate name using shared validation function + const validationError = validateSearchName(name, savedSearches); + if (validationError) { + throw { field: 'name' as const, message: validationError }; + } + + const newSearch: SavedSearch = { + id: uuidv4(), + name, + query, + isDefault: false, + createdAt: Date.now(), + }; + + const newSearches = [...savedSearches, newSearch]; + + await persistSearches(newSearches); + setSavedSearches(newSearches); + + // Track analytics + trackSavedSearchSave({ + hasDefault: newSearches.some((s) => s.isDefault), + totalCount: newSearches.length, + }); + }, + [savedSearches, persistSearches] + ); + + /** + * Rename an existing saved search. + * @throws ValidationError if the new name is not unique + */ + const renameSearch = useCallback( + async (id: string, newName: string): Promise => { + // Validate name using shared validation function (excluding current item) + const validationError = validateSearchName(newName, savedSearches, id); + if (validationError) { + throw { field: 'name' as const, message: validationError }; + } + + const newSearches = savedSearches.map((s) => (s.id === id ? { ...s, name: newName } : s)); + + await persistSearches(newSearches); + setSavedSearches(newSearches); + + // Track analytics + trackSavedSearchRename(); + }, + [savedSearches, persistSearches] + ); + + /** + * Delete a saved search by ID. + */ + const deleteSearch = useCallback( + async (id: string): Promise => { + const newSearches = savedSearches.filter((s) => s.id !== id); + + await persistSearches(newSearches); + setSavedSearches(newSearches); + + // Track analytics + trackSavedSearchDelete(); + }, + [savedSearches, persistSearches] + ); + + /** + * Set or clear the default search. + * Pass null to clear the current default. + */ + const setDefaultSearch = useCallback( + async (id: string | null): Promise => { + const newSearches = savedSearches.map((s) => ({ + ...s, + isDefault: id === null ? false : s.id === id, + })); + + await persistSearches(newSearches); + setSavedSearches(newSearches); + + // Track analytics + trackSavedSearchSetDefault({ action: id === null ? 'clear' : 'set' }); + }, + [savedSearches, persistSearches] + ); + + // Sort saved searches: default first, then alphabetically by name + const sortedSavedSearches = useMemo(() => sortSavedSearches(savedSearches), [savedSearches]); + + return { + savedSearches: sortedSavedSearches, + isLoading, + saveSearch, + renameSearch, + deleteSearch, + setDefaultSearch, + }; +} + +/** + * Track when a saved search is applied (called from parent component). + * @param search - The search that was applied + */ +export function trackSavedSearchApplied(search: SavedSearch) { + trackSavedSearchApply({ isDefault: search.isDefault }); +} diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 53abfa01d14..7a1dd06e31f 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -2666,6 +2666,35 @@ "text-federated": "Federated", "text-provisioned": "Provisioned" }, + "saved-searches": { + "actions-aria-label": "Actions", + "apply-aria-label": "Apply search \"{{name}}\"", + "apply-tooltip": "Apply this search", + "button-label": "Saved searches", + "cancel": "Cancel", + "default-indicator": "Default search", + "delete": "Delete", + "delete-button": "Delete", + "dropdown-aria-label": "Saved searches", + "empty-state": "No saved searches yet", + "error-load-description": "Your saved searches could not be loaded. Please try refreshing the page.", + "error-load-title": "Failed to load saved searches", + "error-name-duplicate": "A saved search with this name already exists", + "error-name-required": "Name is required", + "error-rename-description": "Your changes could not be saved. Please try again.", + "error-rename-title": "Failed to rename", + "error-save-description": "Your changes could not be saved. Please try again.", + "error-save-title": "Failed to save", + "list-aria-label": "Saved searches list", + "name-placeholder": "Enter a name...", + "remove-default": "Remove default", + "rename": "Rename", + "rename-button": "Rename", + "save-button": "Save", + "save-current-search": "Save current search", + "save-disabled-tooltip": "Enter a search query first", + "set-default": "Set as default" + }, "search": { "property": { "data-source": "Data source", From 56dd1ca86761834ca3076c5f9f07d345029684e0 Mon Sep 17 00:00:00 2001 From: Anna Urbiztondo Date: Fri, 19 Dec 2025 15:40:33 +0100 Subject: [PATCH 258/297] Docs: Note for File Provisioning (#115630) Note --- .../provision-resources/file-path-setup.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/sources/as-code/observability-as-code/provision-resources/file-path-setup.md b/docs/sources/as-code/observability-as-code/provision-resources/file-path-setup.md index 4c0d22c3be9..ca71a0c0d9c 100644 --- a/docs/sources/as-code/observability-as-code/provision-resources/file-path-setup.md +++ b/docs/sources/as-code/observability-as-code/provision-resources/file-path-setup.md @@ -155,15 +155,21 @@ To set up synchronization: 1. You can repeat this process for up to 10 connections. +{{< admonition type="note" >}} + +Optionally, you can export any unmanaged resources into the provisioned folder. See how in [Synchronize with external storage](#synchronize-with-external-storage). + +{{< /admonition >}} + ### Synchronize with external storage -After this one time step, all future updates are automatically saved to the local file path and provisioned back to the instance. +In this step you proceed to synchronize the resources selected in the previous step. Optionally, you can check the **Migrate existing resources** box to migrate your unmanaged dashboards to the provisioned folder. -During the initial synchronization, your dashboards will be temporarily unavailable. No data or configurations will be lost. +Select **Begin synchronization** to start the process. After this one time step, all future updates are automatically saved to the local file path and provisioned back to the instance. + +Note that during the initial synchronization, your dashboards will be temporarily unavailable. No data or configurations will be lost. How long the process takes depends upon the number of resources involved. -Select **Begin synchronization** to start the process. - ### Choose additional settings If you wish, you can make any files synchronized as as **Read only** so no changes can be made to the resources through Grafana. From b2d6bb7a05807fb5d817c463c32bb64ac3338c76 Mon Sep 17 00:00:00 2001 From: Galen Kistler <109082771+gtk-grafana@users.noreply.github.com> Date: Fri, 19 Dec 2025 09:26:36 -0600 Subject: [PATCH 259/297] logsdrilldowndefaultcolumns: require plugins:write for non GET operations (#115639) chore: require plugins:write --- pkg/registry/apps/logsdrilldown/authorizer.go | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/pkg/registry/apps/logsdrilldown/authorizer.go b/pkg/registry/apps/logsdrilldown/authorizer.go index f485937121d..b9ba63a3cd2 100644 --- a/pkg/registry/apps/logsdrilldown/authorizer.go +++ b/pkg/registry/apps/logsdrilldown/authorizer.go @@ -24,26 +24,38 @@ func GetAuthorizer() authorizer.Authorizer { return authorizer.DecisionDeny, "valid user is required", err } + // check if is admin + if u.GetIsGrafanaAdmin() { + return authorizer.DecisionAllow, "isGrafanaAdmin", nil + } + // Auth handling for LogsDrilldownDefaults resource if attr.GetResource() == "logsdrilldowndefaults" { // Allow list and get for everyone if attr.GetVerb() == "list" || attr.GetVerb() == "get" { return authorizer.DecisionAllow, "", nil } - // Only allow admins to update (create, update, patch, delete) - if u.GetIsGrafanaAdmin() { - return authorizer.DecisionAllow, "", nil - } // Deny all other operations for non-admins return authorizer.DecisionDeny, "admin access required", nil } - // check if is admin - if u.GetIsGrafanaAdmin() { - return authorizer.DecisionAllow, "", nil + p := u.GetPermissions() + + // Auth handling for Logs Drilldown default columns + if attr.GetResource() == "logsdrilldowndefaultcolumns" { + // Allow get for all users + if attr.GetVerb() == "get" { + return authorizer.DecisionAllow, "", nil + } + // require plugins:write permissions for other operations + _, ok := p[accesscontrol.PluginRolePrefix+"write"] + if ok { + return authorizer.DecisionAllow, "user has plugins:write", nil + } else { + return authorizer.DecisionDeny, "user missing plugins:write", nil + } } - p := u.GetPermissions() if len(p) == 0 { return authorizer.DecisionDeny, "no permissions", nil } From 7812f783bb44e10cb0aea4cbc36af4c3ca47d854 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Fri, 19 Dec 2025 17:36:46 +0200 Subject: [PATCH 260/297] Provisioning: Enable editing dashboard via JSON model (#115420) * Provisioning: Enable save for json model changes * Do not pass props * Simplify logic and fix warnings * add tests * Show diff for json changes * Add try/catch --- eslint-suppressions.json | 5 -- .../dashboard-scene/scene/DashboardScene.tsx | 41 ++++++++--- .../serialization/DashboardSceneSerializer.ts | 21 ++++-- .../settings/JsonModelEditView.tsx | 15 +++- .../SaveProvisionedDashboardForm.test.tsx | 72 ++++++++++++++++++- .../SaveProvisionedDashboardForm.tsx | 21 +++--- public/locales/en-US/grafana.json | 2 +- 7 files changed, 147 insertions(+), 30 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 36eb78aaa72..597f2b94a1a 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1911,11 +1911,6 @@ "count": 1 } }, - "public/app/features/dashboard-scene/settings/JsonModelEditView.tsx": { - "react/no-unescaped-entities": { - "count": 2 - } - }, "public/app/features/dashboard-scene/settings/variables/VariableEditableElement.tsx": { "react-hooks/rules-of-hooks": { "count": 4 diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 82a20dbcd52..7ddd7c4e779 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -59,6 +59,7 @@ import { gridItemToGridLayoutItemKind } from '../serialization/layoutSerializers import { getElement } from '../serialization/layoutSerializers/utils'; import { buildGridItemForPanel, transformSaveModelToScene } from '../serialization/transformSaveModelToScene'; import { gridItemToPanel } from '../serialization/transformSceneToSaveModel'; +import { JsonModelEditView } from '../settings/JsonModelEditView'; import { DecoratedRevisionModel } from '../settings/VersionsEditView'; import { DashboardEditView } from '../settings/utils'; import { historySrv } from '../settings/version-history/HistorySrv'; @@ -855,30 +856,54 @@ export class DashboardScene extends SceneObjectBase impleme return this.serializer.getSaveModel(this); } - // Get the dashboard in native K8s form (using the appropriate apiVersion) - getSaveResource(options: SaveDashboardAsOptions): ResourceForCreate { + // Helper method to build K8s resource structure + private buildResourceForCreate(spec: Dashboard | DashboardV2Spec, isNew: boolean): ResourceForCreate { const { meta } = this.state; - const spec = this.getSaveAsModel(options); - - const apiVersion = this.serializer instanceof V2DashboardSerializer ? 'v2beta1' : 'v1beta1'; // get from the dashboard? + const apiVersion = this.serializer instanceof V2DashboardSerializer ? 'v2beta1' : 'v1beta1'; return { apiVersion: `dashboard.grafana.app/${apiVersion}`, kind: 'Dashboard', metadata: { ...meta.k8s, - name: options.isNew ? undefined : (meta.uid ?? meta.k8s?.name), - generateName: options.isNew ? 'd' : undefined, + name: isNew ? undefined : (meta.uid ?? meta.k8s?.name), + generateName: isNew ? 'd' : undefined, }, spec, }; } + // Get the dashboard in native K8s form (using the appropriate apiVersion) + getSaveResource(options: SaveDashboardAsOptions): ResourceForCreate { + const spec = this.getSaveAsModel(options); + return this.buildResourceForCreate(spec, options.isNew ?? false); + } + + // Wrap a raw dashboard spec in K8s resource format + // Used by JSON model editor for Git sync dashboards + getSaveResourceFromSpec(rawSpec: Dashboard | DashboardV2Spec): ResourceForCreate { + return this.buildResourceForCreate(rawSpec, false); + } + + // Get raw JSON from JSON model editor if currently active + // Returns undefined if not in JSON editor mode or if JSON is invalid + getRawJsonFromEditor(): Dashboard | DashboardV2Spec | undefined { + if (this.state.editview instanceof JsonModelEditView) { + try { + return JSON.parse(this.state.editview.state.jsonText); + } catch { + return undefined; + } + } + return undefined; + } + getSaveAsModel(options: SaveDashboardAsOptions): Dashboard | DashboardV2Spec { return this.serializer.getSaveAsModel(this, options); } getDashboardChanges(saveTimeRange?: boolean, saveVariables?: boolean, saveRefresh?: boolean): DashboardChangeInfo { - return this.serializer.getDashboardChangesFromScene(this, { saveTimeRange, saveVariables, saveRefresh }); + const rawJson = this.getRawJsonFromEditor(); + return this.serializer.getDashboardChangesFromScene(this, { saveTimeRange, saveVariables, saveRefresh, rawJson }); } getManagerKind(): ManagerKind | undefined { diff --git a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts index cede8f605a5..130c5450bf0 100644 --- a/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts +++ b/public/app/features/dashboard-scene/serialization/DashboardSceneSerializer.ts @@ -46,6 +46,7 @@ export interface DashboardSceneSerializerLike DashboardChangeInfo; onSaveComplete(saveModel: T, result: SaveDashboardResponseDTO): void; @@ -184,9 +185,15 @@ export class V1DashboardSerializer getDashboardChangesFromScene( scene: DashboardScene, - options: { saveTimeRange?: boolean; saveVariables?: boolean; saveRefresh?: boolean } + options: { + saveTimeRange?: boolean; + saveVariables?: boolean; + saveRefresh?: boolean; + rawJson?: Dashboard | DashboardV2Spec; + } ) { - const changedSaveModel = this.getSaveModel(scene); + const changedSaveModel = + options.rawJson && !isDashboardV2Spec(options.rawJson) ? options.rawJson : this.getSaveModel(scene); const changeInfo = getRawDashboardChanges( this.initialSaveModel!, changedSaveModel, @@ -396,9 +403,15 @@ export class V2DashboardSerializer getDashboardChangesFromScene( scene: DashboardScene, - options: { saveTimeRange?: boolean; saveVariables?: boolean; saveRefresh?: boolean } + options: { + saveTimeRange?: boolean; + saveVariables?: boolean; + saveRefresh?: boolean; + rawJson?: Dashboard | DashboardV2Spec; + } ) { - const changedSaveModel = this.getSaveModel(scene); + const changedSaveModel = + options.rawJson && isDashboardV2Spec(options.rawJson) ? options.rawJson : this.getSaveModel(scene); const changeInfo = getRawDashboardV2Changes( this.initialSaveModel!, changedSaveModel, diff --git a/public/app/features/dashboard-scene/settings/JsonModelEditView.tsx b/public/app/features/dashboard-scene/settings/JsonModelEditView.tsx index 30979075d2a..99522077ae3 100644 --- a/public/app/features/dashboard-scene/settings/JsonModelEditView.tsx +++ b/public/app/features/dashboard-scene/settings/JsonModelEditView.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; -import { SceneComponentProps, SceneObjectBase, sceneUtils } from '@grafana/scenes'; +import { SceneComponentProps, SceneObjectBase, SceneObjectRef, sceneUtils } from '@grafana/scenes'; import { Dashboard } from '@grafana/schema'; import { Spec as DashboardV2Spec } from '@grafana/schema/dist/esm/schema/dashboard/v2'; import { Alert, Box, Button, CodeEditor, Stack, useStyles2 } from '@grafana/ui'; @@ -11,8 +11,10 @@ import { Page } from 'app/core/components/Page/Page'; import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api'; import { isDashboardV2Spec } from 'app/features/dashboard/api/utils'; import { getPrettyJSON } from 'app/features/inspector/utils/utils'; +import { useIsProvisionedNG } from 'app/features/provisioning/hooks/useIsProvisionedNG'; import { DashboardDataDTO, SaveDashboardResponseDTO } from 'app/types/dashboard'; +import { SaveDashboardDrawer } from '../saving/SaveDashboardDrawer'; import { NameAlreadyExistsError, isNameExistsError, @@ -105,12 +107,21 @@ function JsonModelEditViewComponent({ model }: SceneComponentProps { + if (isProvisionedNG) { + const drawer = new SaveDashboardDrawer({ + dashboardRef: new SceneObjectRef(dashboard), + }); + dashboard.setState({ overlay: drawer }); + return; + } + const result = await onSaveDashboard(dashboard, { folderUid: dashboard.state.meta.folderUid, overwrite, @@ -136,7 +147,7 @@ function JsonModelEditViewComponent({ model }: SceneComponentProps {overwrite ? ( - 'Save and overwrite' + Save and overwrite ) : ( Save changes )} diff --git a/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.test.tsx b/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.test.tsx index bca9ca1939c..69a94c5e9ae 100644 --- a/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.test.tsx +++ b/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.test.tsx @@ -9,7 +9,7 @@ import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScen import { validationSrv } from 'app/features/manage-dashboards/services/ValidationSrv'; import { useCreateOrUpdateRepositoryFile } from 'app/features/provisioning/hooks/useCreateOrUpdateRepositoryFile'; -import { SaveProvisionedDashboardForm, Props } from './SaveProvisionedDashboardForm'; +import { Props, SaveProvisionedDashboardForm } from './SaveProvisionedDashboardForm'; jest.mock('@grafana/runtime', () => { const actual = jest.requireActual('@grafana/runtime'); @@ -118,6 +118,7 @@ function setup(props: Partial = {}) { closeModal: jest.fn(), getSaveAsModel: jest.fn().mockReturnValue(mockDashboard), setManager: jest.fn(), + getRawJsonFromEditor: jest.fn().mockReturnValue(undefined), } as unknown as DashboardScene, drawer: { onClose: jest.fn(), @@ -291,6 +292,7 @@ describe('SaveProvisionedDashboardForm', () => { closeModal: jest.fn(), getSaveResource: jest.fn().mockReturnValue(updatedDashboard), setManager: jest.fn(), + getRawJsonFromEditor: jest.fn().mockReturnValue(undefined), } as unknown as DashboardScene, }); @@ -385,6 +387,7 @@ describe('SaveProvisionedDashboardForm', () => { closeModal: jest.fn(), getSaveAsModel: jest.fn().mockReturnValue({}), setManager: jest.fn(), + getRawJsonFromEditor: jest.fn().mockReturnValue(undefined), } as unknown as DashboardScene, }); @@ -431,6 +434,7 @@ describe('SaveProvisionedDashboardForm', () => { closeModal: jest.fn(), getSaveAsModel: jest.fn().mockReturnValue({}), setManager: jest.fn(), + getRawJsonFromEditor: jest.fn().mockReturnValue(undefined), } as unknown as DashboardScene, }); @@ -445,4 +449,70 @@ describe('SaveProvisionedDashboardForm', () => { expect(saveButton).toBeEnabled(); }); }); + + it('should save dashboard with raw JSON from editor', async () => { + const mockAction = jest.fn(); + const mockRequest = { ...mockRequestBase, isSuccess: true }; + (useCreateOrUpdateRepositoryFile as jest.Mock).mockReturnValue([mockAction, mockRequest]); + + const rawJson = JSON.stringify({ + title: 'Raw JSON Dashboard', + panels: [], + schemaVersion: 36, + }); + + const dashboardFromRawJson = { + apiVersion: 'dashboard.grafana.app/v1alpha1', + kind: 'Dashboard', + metadata: { + generateName: 'p', + name: undefined, + }, + spec: { + title: 'Raw JSON Dashboard', + panels: [], + schemaVersion: 36, + }, + }; + + const { user } = setup({ + dashboard: { + useState: () => ({ + meta: { + folderUid: 'folder-uid', + slug: 'test-dashboard', + }, + title: 'Test Dashboard', + description: 'Test Description', + isDirty: false, + }), + setState: jest.fn(), + closeModal: jest.fn(), + getSaveAsModel: jest.fn().mockReturnValue({}), + getSaveResource: jest.fn().mockReturnValue(dashboardFromRawJson), + getSaveResourceFromSpec: jest.fn().mockReturnValue(dashboardFromRawJson), + setManager: jest.fn(), + getRawJsonFromEditor: jest.fn().mockReturnValue(rawJson), + } as unknown as DashboardScene, + }); + + const saveButton = screen.getByRole('button', { name: /save/i }); + expect(saveButton).toBeEnabled(); + + const commentInput = screen.getByRole('textbox', { name: /comment/i }); + await user.clear(commentInput); + await user.type(commentInput, 'Save with raw JSON'); + + await user.click(saveButton); + + await waitFor(() => { + expect(mockAction).toHaveBeenCalledWith({ + ref: 'dashboard/2023-01-01-abcde', + name: 'test-repo', + path: 'test-dashboard.json', + message: 'Save with raw JSON', + body: dashboardFromRawJson, + }); + }); + }); }); diff --git a/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx b/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx index a2df43a1f95..3fee476b189 100644 --- a/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx +++ b/public/app/features/provisioning/components/Dashboards/SaveProvisionedDashboardForm.tsx @@ -65,8 +65,9 @@ export function SaveProvisionedDashboardForm({ register, formState: { dirtyFields }, } = methods; - // button enabled if form comment is dirty or dashboard state is dirty - const isDirtyState = Boolean(dirtyFields.comment) || isDirty; + // button enabled if form comment is dirty or dashboard state is dirty or raw JSON was provided from editor + const rawDashboardJSON = dashboard.getRawJsonFromEditor(); + const isDirtyState = Boolean(dirtyFields.comment) || isDirty || Boolean(rawDashboardJSON); const [workflow, ref, path] = watch(['workflow', 'ref', 'path']); // Update the form if default values change @@ -191,13 +192,15 @@ export function SaveProvisionedDashboardForm({ const message = comment || `Save dashboard: ${dashboard.state.title}`; - const body = dashboard.getSaveResource({ - isNew, - title, - description, - copyTags, - saveAsCopy, - }); + const body = rawDashboardJSON + ? dashboard.getSaveResourceFromSpec(rawDashboardJSON) + : dashboard.getSaveResource({ + isNew, + title, + description, + copyTags, + saveAsCopy, + }); reportInteraction('grafana_provisioning_dashboard_save_submitted', { workflow, diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 7a1dd06e31f..8e4ed2c7b20 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -6210,7 +6210,7 @@ "title-someone-else-has-updated-this-dashboard": "Someone else has updated this dashboard", "would-still-dashboard": "Would you still like to save this dashboard?" }, - "save-and-overwrite": "'Save and overwrite'" + "save-and-overwrite": "Save and overwrite" }, "library-viz-panel-info": { "last-edited": "{{timeAgo}} by ", From aa69d97f1eb6a871fa1d7dc3d82f207c1754652d Mon Sep 17 00:00:00 2001 From: Kristina Demeshchik Date: Fri, 19 Dec 2025 10:38:22 -0500 Subject: [PATCH 261/297] Variables: Show variable reference instead of interpolated datasource in query variable editor (#115624) show variable ref --- .../settings/variables/editors/QueryVariableEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/settings/variables/editors/QueryVariableEditor.tsx b/public/app/features/dashboard-scene/settings/variables/editors/QueryVariableEditor.tsx index c32bc8eda8d..3f0583d3245 100644 --- a/public/app/features/dashboard-scene/settings/variables/editors/QueryVariableEditor.tsx +++ b/public/app/features/dashboard-scene/settings/variables/editors/QueryVariableEditor.tsx @@ -265,7 +265,7 @@ export function Editor({ variable }: { variable: QueryVariable }) { htmlFor="data-source-picker" noMargin > - + {selectedDatasource && VariableQueryEditor && ( From 0e4b1c7b1e23c5ad40669fdd6ee4b28b6e33cfb4 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Fri, 19 Dec 2025 17:57:45 +0200 Subject: [PATCH 262/297] Provisioning: Fix error loop in synchronise step (#115570) * Refactor requiresMigration * Remove InlineSecureValueWarning * Prevent error loop * Fix error loop * Cleanup * i18n --- .../utils/createOnCacheEntryAdded.ts | 1 + .../provisioning/Config/ConfigForm.tsx | 2 -- public/app/features/provisioning/HomePage.tsx | 2 -- .../provisioning/Job/FinishedJobStatus.tsx | 5 +++ .../features/provisioning/Job/JobStatus.tsx | 21 ++++++++---- .../Repository/RepositoryStatusPage.tsx | 9 ++++-- .../Wizard/hooks/useResourceStats.ts | 18 ++--------- .../components/InlineSecureValueWarning.tsx | 32 ------------------- .../features/provisioning/utils/httpUtils.ts | 3 ++ public/locales/en-US/grafana.json | 4 ++- 10 files changed, 37 insertions(+), 60 deletions(-) delete mode 100644 public/app/features/provisioning/components/InlineSecureValueWarning.tsx diff --git a/public/app/api/clients/provisioning/utils/createOnCacheEntryAdded.ts b/public/app/api/clients/provisioning/utils/createOnCacheEntryAdded.ts index c3c44ede267..0d39d4fa402 100644 --- a/public/app/api/clients/provisioning/utils/createOnCacheEntryAdded.ts +++ b/public/app/api/clients/provisioning/utils/createOnCacheEntryAdded.ts @@ -58,6 +58,7 @@ export function createOnCacheEntryAdded(resourceName: string) { }); } catch (error) { console.error('Error in onCacheEntryAdded:', error); + return; } await cacheEntryRemoved; diff --git a/public/app/features/provisioning/Config/ConfigForm.tsx b/public/app/features/provisioning/Config/ConfigForm.tsx index 9c86403fee6..3b1aebc1911 100644 --- a/public/app/features/provisioning/Config/ConfigForm.tsx +++ b/public/app/features/provisioning/Config/ConfigForm.tsx @@ -28,7 +28,6 @@ import { FormPrompt } from 'app/core/components/FormPrompt/FormPrompt'; import { DeleteRepositoryButton } from '../Repository/DeleteRepositoryButton'; import { TokenPermissionsInfo } from '../Shared/TokenPermissionsInfo'; import { getGitProviderFields, getLocalProviderFields } from '../Wizard/fields'; -import { InlineSecureValueWarning } from '../components/InlineSecureValueWarning'; import { PROVISIONING_URL } from '../constants'; import { useCreateOrUpdateRepository } from '../hooks/useCreateOrUpdateRepository'; import { RepositoryFormData } from '../types'; @@ -178,7 +177,6 @@ export function ConfigForm({ data }: ConfigFormProps) { {gitFields && ( <> - } > - diff --git a/public/app/features/provisioning/Job/JobStatus.tsx b/public/app/features/provisioning/Job/JobStatus.tsx index 02e0b733bbb..7aec776f8aa 100644 --- a/public/app/features/provisioning/Job/JobStatus.tsx +++ b/public/app/features/provisioning/Job/JobStatus.tsx @@ -1,8 +1,11 @@ +import { useEffect } from 'react'; + import { Trans, t } from '@grafana/i18n'; import { Spinner, Stack, Text } from '@grafana/ui'; import { Job, useListJobQuery } from 'app/api/clients/provisioning/v0alpha1'; import { StepStatusInfo } from '../Wizard/types'; +import { getErrorMessage } from '../utils/httpUtils'; import { FinishedJobStatus } from './FinishedJobStatus'; import { JobContent } from './JobContent'; @@ -25,6 +28,18 @@ export function JobStatus({ jobType, watch, onStatusChange }: JobStatusProps) { const activeQueryCompleted = !activeQuery.isUninitialized && !activeQuery.isLoading; const shouldCheckFinishedJobs = activeQueryCompleted && !activeJob && !!repoLabel; + useEffect(() => { + if (activeQuery.isError) { + onStatusChange?.({ + status: 'error', + error: { + title: t('provisioning.job-status.title.error-fetching-active-job', 'Error fetching active job'), + message: getErrorMessage(activeQuery.error), + }, + }); + } + }, [activeQuery.isError, activeQuery.error, onStatusChange]); + if (activeQuery.isLoading) { return ( @@ -37,12 +52,6 @@ export function JobStatus({ jobType, watch, onStatusChange }: JobStatusProps) { } if (activeQuery.isError) { - onStatusChange?.({ - status: 'error', - error: { - title: t('provisioning.job-status.title.error-fetching-active-job', 'Error fetching active job'), - }, - }); return null; } diff --git a/public/app/features/provisioning/Repository/RepositoryStatusPage.tsx b/public/app/features/provisioning/Repository/RepositoryStatusPage.tsx index a8984eaa51f..e18e47500da 100644 --- a/public/app/features/provisioning/Repository/RepositoryStatusPage.tsx +++ b/public/app/features/provisioning/Repository/RepositoryStatusPage.tsx @@ -10,8 +10,8 @@ import { Page } from 'app/core/components/Page/Page'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { isNotFoundError } from 'app/features/alerting/unified/api/util'; -import { InlineSecureValueWarning } from '../components/InlineSecureValueWarning'; import { PROVISIONING_URL } from '../constants'; +import { getErrorMessage } from '../utils/httpUtils'; import { RepositoryActions } from './RepositoryActions'; import { RepositoryOverview } from './RepositoryOverview'; @@ -36,6 +36,7 @@ export default function RepositoryStatusPage() { const tab = queryParams['tab'] ?? TabSelection.Overview; const notFound = query.isError && isNotFoundError(query.error); + const hasError = query.isError && !notFound; const tabInfo = useMemo>( () => [ @@ -63,7 +64,11 @@ export default function RepositoryStatusPage() { actions={data && } > - + {hasError && ( + + {getErrorMessage(query.error)} + + )} {notFound ? ( 0; - - // Calculate final requiresMigration based on sync target and user selection - // For instance sync: always use baseRequiresMigration (checkbox is disabled and always true) + // Calculate requiresMigration based on sync target and user selection + // For instance sync: migrate if there are resources (checkbox is disabled and always true) // For folder sync: only migrate if user explicitly opts in via checkbox - const requiresMigration = useMemo(() => { - if (syncTarget === 'instance') { - return baseRequiresMigration; - } - if (syncTarget === 'folder') { - return migrateResources ?? false; - } - return baseRequiresMigration; - }, [syncTarget, baseRequiresMigration, migrateResources]); - + const requiresMigration = syncTarget === 'instance' ? resourceCount > 0 : (migrateResources ?? false); const shouldSkipSync = (resourceCount === 0 || syncTarget === 'folder') && fileCount === 0; // Format display strings diff --git a/public/app/features/provisioning/components/InlineSecureValueWarning.tsx b/public/app/features/provisioning/components/InlineSecureValueWarning.tsx deleted file mode 100644 index 9ed4a23b7b1..00000000000 --- a/public/app/features/provisioning/components/InlineSecureValueWarning.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { t } from '@grafana/i18n'; -import { Alert } from '@grafana/ui'; -import { Repository } from 'app/api/clients/provisioning/v0alpha1'; - -interface Props { - repo?: Repository; - items?: Repository[]; -} - -// TODO: remove this after 12.2 -export function InlineSecureValueWarning({ repo, items }: Props) { - const isRepoValid = (r?: Repository) => r?.spec?.type === 'local' || !!r?.secure?.token?.name; - - if (isRepoValid(repo)) { - return null; - } - - // When a list is passed in, show an error if anything is missing - if (items?.every(isRepoValid)) { - return null; - } - - return ( - - ); -} diff --git a/public/app/features/provisioning/utils/httpUtils.ts b/public/app/features/provisioning/utils/httpUtils.ts index 6e9cbc4e933..3fb6bd7704f 100644 --- a/public/app/features/provisioning/utils/httpUtils.ts +++ b/public/app/features/provisioning/utils/httpUtils.ts @@ -1,4 +1,5 @@ import { t } from '@grafana/i18n'; +import { isFetchError } from '@grafana/runtime'; import { HttpError, isHttpError } from '../guards'; @@ -187,6 +188,8 @@ export function getErrorMessage(err: unknown) { } else if (err.message) { errorMessage = err.message; } + } else if (isFetchError(err)) { + errorMessage = err.data.message; } return errorMessage; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 8e4ed2c7b20..9678b91e619 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -11985,7 +11985,6 @@ "resource-not-found": "Resource not found. Please check the URL or repository.", "unsupported-repository-type": "Unsupported repository type: {{repositoryType}}" }, - "inline-secure-values-warning": "You need to save your access tokens again due to a system update", "instance-sync-deprecation": { "message": "Instance sync is currently not fully supported and breaks library panels and alerts. To use library panels and alerts, disconnect your repository and reconnect it using folder sync instead.", "title": "Instance sync is not fully supported" @@ -12095,6 +12094,9 @@ "webhook-last-event": "Last Event:", "webhook-url": "View Webhook" }, + "repository-status": { + "error": "Failed to load repository" + }, "repository-status-page": { "back-to-repositories": "Back to repositories", "cleaning-up-resources": "Cleaning up repository resources", From fa73caf6c84062d9437c577d21e5a65f806af386 Mon Sep 17 00:00:00 2001 From: Collin Fingar Date: Fri, 19 Dec 2025 11:30:24 -0500 Subject: [PATCH 263/297] Snapshots: Fix V2 Snapshot data coupling (#115278) * Snapshots: Potential fix for rendering V2 snaps * removing comments * Added unit test --- .../transformSceneToSaveModelSchemaV2.test.ts | 102 +++++++++++++++++- .../transformSceneToSaveModelSchemaV2.ts | 72 ++++++++++--- 2 files changed, 160 insertions(+), 14 deletions(-) diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts index 6d0c450add3..4dcc732fb01 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts @@ -1,4 +1,4 @@ -import { VariableRefresh } from '@grafana/data'; +import { VariableRefresh, PanelData, LoadingState, toDataFrame, FieldType, getDefaultTimeRange } from '@grafana/data'; import { config } from '@grafana/runtime'; import { AdHocFiltersVariable, @@ -18,6 +18,8 @@ import { VizPanel, SceneDataQuery, SceneQueryRunner, + SceneDataTransformer, + SceneDataNode, sceneUtils, dataLayers, } from '@grafana/scenes'; @@ -33,6 +35,7 @@ import { TabsLayoutSpec, defaultDataQueryKind, } from '@grafana/schema/dist/esm/schema/dashboard/v2'; +import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; import { DashboardEditPane } from '../edit-pane/DashboardEditPane'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; @@ -954,6 +957,103 @@ describe('getVizPanelQueries', () => { expect(result[1].spec.query.group).toBe('prometheus'); expect(result[1].spec.query.version).toBe('v0'); }); + + describe('snapshot mode', () => { + it('should return empty queries when isSnapshot is true but panel has no data provider', () => { + const vizPanel = new VizPanel({ + key: 'panel-1', + pluginId: 'timeseries', + // No $data provider + }); + + const result = getVizPanelQueries(vizPanel, undefined, true); + expect(result).toEqual([]); + }); + + it('should create snapshot query from SceneQueryRunner data when isSnapshot is true', () => { + const mockDataFrame = toDataFrame({ + name: 'test-series', + fields: [ + { name: 'time', type: FieldType.time, values: [1000, 2000, 3000] }, + { name: 'value', type: FieldType.number, values: [1, 2, 3] }, + ], + }); + + const panelData: PanelData = { + series: [mockDataFrame], + state: LoadingState.Done, + timeRange: getDefaultTimeRange(), + }; + + const queryRunner = new SceneQueryRunner({ + queries: [], + data: panelData, + }); + + const vizPanel = new VizPanel({ + key: 'panel-1', + pluginId: 'timeseries', + $data: queryRunner, + }); + + const result = getVizPanelQueries(vizPanel, undefined, true); + + expect(result).toHaveLength(1); + expect(result[0].kind).toBe('PanelQuery'); + expect(result[0].spec.refId).toBe('A'); + expect(result[0].spec.hidden).toBe(false); + expect(result[0].spec.query.kind).toBe('DataQuery'); + expect(result[0].spec.query.version).toBe(defaultDataQueryKind().version); + expect(result[0].spec.query.group).toBe('grafana'); + expect(result[0].spec.query.datasource).toEqual({ name: 'grafana' }); + expect(result[0].spec.query.spec.queryType).toBe(GrafanaQueryType.Snapshot); + expect(result[0].spec.query.spec.snapshot).toBeDefined(); + expect(result[0].spec.query.spec.snapshot).toHaveLength(1); + expect(result[0].spec.query.spec.snapshot[0].schema?.fields).toBeDefined(); + }); + + it('should create snapshot query from SceneDataTransformer data when isSnapshot is true', () => { + const mockDataFrame = toDataFrame({ + name: 'transformed-series', + fields: [ + { name: 'time', type: FieldType.time, values: [1000, 2000] }, + { name: 'transformed', type: FieldType.number, values: [10, 20] }, + ], + }); + + const panelData: PanelData = { + series: [mockDataFrame], + state: LoadingState.Done, + timeRange: getDefaultTimeRange(), + }; + + const dataNode = new SceneDataNode({ + data: panelData, + }); + + const dataTransformer = new SceneDataTransformer({ + $data: dataNode, + transformations: [], + }); + + const vizPanel = new VizPanel({ + key: 'panel-1', + pluginId: 'timeseries', + $data: dataTransformer, + }); + + const result = getVizPanelQueries(vizPanel, undefined, true); + + expect(result).toHaveLength(1); + expect(result[0].kind).toBe('PanelQuery'); + expect(result[0].spec.query.kind).toBe('DataQuery'); + expect(result[0].spec.query.spec.queryType).toBe(GrafanaQueryType.Snapshot); + expect(result[0].spec.query.spec.snapshot).toBeDefined(); + expect(result[0].spec.query.spec.snapshot).toHaveLength(1); + // Verify it gets data from the nested $data (SceneDataNode) not the transformer + expect(result[0].spec.query.spec.snapshot[0].schema?.fields).toBeDefined(); + }); + }); }); function getMinimalSceneState(body: DashboardLayoutManager): Partial { diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index 4a63c62af93..0ef2a5e5f05 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -13,8 +13,10 @@ import { SceneVariableSet, VizPanel, } from '@grafana/scenes'; -import { DataSourceRef, VariableRefresh } from '@grafana/schema'; +import { DataSourceRef } from '@grafana/schema'; import { sortedDeepCloneWithoutNulls } from 'app/core/utils/object'; +import { getPanelDataFrames } from 'app/features/dashboard/components/HelpWizard/utils'; +import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; import { Spec as DashboardV2Spec, @@ -127,7 +129,7 @@ export function transformSceneToSaveModelSchemaV2(scene: DashboardScene, isSnaps // EOF variables // elements - elements: getElements(scene, dsReferencesMapping), + elements: getElements(scene, dsReferencesMapping, isSnapshot), // EOF elements // annotations @@ -170,17 +172,18 @@ function getLiveNow(state: DashboardSceneState) { return Boolean(liveNow); } -function getElements(scene: DashboardScene, dsReferencesMapping?: DSReferencesMapping) { +function getElements(scene: DashboardScene, dsReferencesMapping?: DSReferencesMapping, isSnapshot = false) { const panels = scene.state.body.getVizPanels() ?? []; const panelsArray = panels.map((vizPanel) => { - return vizPanelToSchemaV2(vizPanel, dsReferencesMapping); + return vizPanelToSchemaV2(vizPanel, dsReferencesMapping, isSnapshot); }); return createElements(panelsArray, scene); } export function vizPanelToSchemaV2( vizPanel: VizPanel, - dsReferencesMapping?: DSReferencesMapping + dsReferencesMapping?: DSReferencesMapping, + isSnapshot = false ): PanelKind | LibraryPanelKind { if (isLibraryPanel(vizPanel)) { const behavior = getLibraryPanelBehavior(vizPanel)!; @@ -216,7 +219,7 @@ export function vizPanelToSchemaV2( data: { kind: 'QueryGroup', spec: { - queries: getVizPanelQueries(vizPanel, dsReferencesMapping), + queries: getVizPanelQueries(vizPanel, dsReferencesMapping, isSnapshot), transformations: getVizPanelTransformations(vizPanel), queryOptions: getVizPanelQueryOptions(vizPanel), }, @@ -290,9 +293,51 @@ function getPanelLinks(panel: VizPanel): DataLink[] { return []; } -export function getVizPanelQueries(vizPanel: VizPanel, dsReferencesMapping?: DSReferencesMapping): PanelQueryKind[] { +export function getVizPanelQueries( + vizPanel: VizPanel, + dsReferencesMapping?: DSReferencesMapping, + isSnapshot = false +): PanelQueryKind[] { const queries: PanelQueryKind[] = []; const queryRunner = getQueryRunnerFor(vizPanel); + + if (isSnapshot) { + const dataProvider = vizPanel.state.$data; + if (!dataProvider) { + return queries; + } + + let snapshotData = getPanelDataFrames(dataProvider.state.data); + if (dataProvider instanceof SceneDataTransformer) { + snapshotData = getPanelDataFrames(dataProvider.state.$data!.state.data); + } + + const snapshotQuery: DataQueryKind = { + kind: 'DataQuery', + version: defaultDataQueryKind().version, + group: 'grafana', + datasource: { + name: 'grafana', + }, + spec: { + queryType: GrafanaQueryType.Snapshot, + snapshot: snapshotData, + }, + }; + + queries.push({ + kind: 'PanelQuery', + spec: { + query: snapshotQuery, + refId: 'A', + hidden: false, + }, + }); + + return queries; + } + + // Regular query handling (non-snapshot) const vizPanelQueries = queryRunner?.state.queries; if (vizPanelQueries) { @@ -631,15 +676,16 @@ export function trimDashboardForSnapshot(title: string, time: TimeRange, dash: D if (spec.variables) { spec.variables.forEach((variable) => { - if ('query' in variable) { - variable.query = ''; + if ('query' in variable.spec) { + variable.spec.query = ''; } - if ('options' in variable && 'current' in variable) { - variable.options = variable.current && !isEmptyObject(variable.current) ? [variable.current] : []; + if ('options' in variable.spec && 'current' in variable.spec) { + variable.spec.options = + variable.spec.current && !isEmptyObject(variable.spec.current) ? [variable.spec.current] : []; } - if ('refresh' in variable) { - variable.refresh = VariableRefresh.never; + if ('refresh' in variable.spec) { + variable.spec.refresh = 'never'; } }); } From 8b316cca250d6976893a655fa51874f73f0aef10 Mon Sep 17 00:00:00 2001 From: Rodrigo Vasconcelos de Barros Date: Fri, 19 Dec 2025 11:39:48 -0500 Subject: [PATCH 264/297] Alerting: Add tests for AlertRuleMenu component (#115473) * Alerting: Add tests for AlertRuleMenu component * Refactor test mocks according TESTING.md * Remove duplicate mock functions * Replace snapshot test with more readable assertion * Remove SETUP_ALERTING_DEV.md file * Refactor feature flags usage in tests --- .../rule-viewer/AlertRuleMenu.test.tsx | 1612 +++++++++++++++++ 1 file changed, 1612 insertions(+) create mode 100644 public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.test.tsx diff --git a/public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.test.tsx b/public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.test.tsx new file mode 100644 index 00000000000..e95b321c02c --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-viewer/AlertRuleMenu.test.tsx @@ -0,0 +1,1612 @@ +import { render, screen, testWithFeatureToggles, userEvent, waitFor } from 'test/test-utils'; +import { byLabelText, byRole } from 'testing-library-selector'; + +import { useAssistant } from '@grafana/assistant'; +import { GrafanaEdition } from '@grafana/data/internal'; +import { config, setPluginLinksHook } from '@grafana/runtime'; +import AlertRuleMenu from 'app/features/alerting/unified/components/rule-viewer/AlertRuleMenu'; +import { mockFolderApi, setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { + getCloudRule, + getGrafanaRule, + grantUserPermissions, + mockDataSource, + mockFolder, + mockGrafanaRulerRule, + mockPromAlertingRule, + mockPromRecordingRule, + mockRulerGrafanaRecordingRule, +} from 'app/features/alerting/unified/mocks'; +import { setFolderAccessControl } from 'app/features/alerting/unified/mocks/server/configure'; +import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources'; +import * as miscUtils from 'app/features/alerting/unified/utils/misc'; +import { fromCombinedRule } from 'app/features/alerting/unified/utils/rule-id'; +import { AccessControlAction } from 'app/types/accessControl'; +import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto'; + +import { AlertRuleAction } from '../../hooks/useAbilities'; + +const mockOpenAssistant = jest.fn(); +jest.mock('@grafana/assistant', () => ({ + useAssistant: jest.fn(), + createAssistantContextItem: jest.fn((type: string, data: T) => ({ + type, + ...data, + })), +})); + +const mockUseAssistant = jest.mocked(useAssistant); + +const mockPauseExecute = jest.fn().mockResolvedValue(undefined); +jest.mock('../../hooks/ruleGroup/usePauseAlertRule', () => ({ + usePauseRuleInGroup: () => [ + { + execute: mockPauseExecute, + }, + { loading: false, error: undefined }, + ], +})); + +const server = setupMswServer(); + +setPluginLinksHook(() => ({ + links: [], + isLoading: false, +})); + +setupDataSources(); + +const user = userEvent.setup(); +const handleSilence = jest.fn(); +const handleDelete = jest.fn(); +const handleDuplicateRule = jest.fn(); + +const ui = { + moreButton: byLabelText(/More/), + menu: byRole('menu'), + menuItems: { + pause: byRole('menuitem', { name: /Pause evaluation/i }), + resume: byRole('menuitem', { name: /Resume evaluation/i }), + silence: byRole('menuitem', { name: /Silence notifications/i }), + duplicate: byRole('menuitem', { name: /Duplicate/i }), + copyLink: byRole('menuitem', { name: /Copy link/i }), + export: byRole('menuitem', { name: /Export/i }), + delete: byRole('menuitem', { name: /Delete/i }), + manageEnrichments: byRole('menuitem', { name: /Manage enrichments/i }), + declareIncident: byRole('link', { name: /Declare incident/i }), + analyzeRule: byRole('menuitem', { name: /Analyze rule/i }), + }, +}; + +const getMenuContents = async () => { + await screen.findByRole('menu'); + const allMenuItems = screen.queryAllByRole('menuitem').map((el) => el.textContent); + const allLinkItems = screen.queryAllByRole('link').map((el) => el.textContent); + + return [...allMenuItems, ...allLinkItems]; +}; + +const openMenu = async () => { + await user.click(await ui.moreButton.find()); + await waitFor(() => { + expect(ui.menu.query()).toBeInTheDocument(); + }); +}; + +// Helper function to create a default rule setup +const createDefaultRuleSetup = () => { + const mockRule = getGrafanaRule(); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + return { mockRule, identifier, groupIdentifier }; +}; + +describe('AlertRuleMenu', () => { + const originalBuildInfo = config.buildInfo; + + beforeEach(() => { + jest.clearAllMocks(); + mockPauseExecute.mockResolvedValue(undefined); + mockOpenAssistant.mockClear(); + // Default: assistant unavailable + mockUseAssistant.mockReturnValue({ + isAvailable: false, + openAssistant: mockOpenAssistant, + } as unknown as ReturnType); + + // Reset config to defaults + config.buildInfo = { ...originalBuildInfo }; + + // Set up default folder mock for Grafana rules (namespace-uid is the default folder UID) + mockFolderApi(server).folder('namespace-uid', mockFolder({ uid: 'namespace-uid', title: 'Test Folder' })); + + // Set up default permissions (no permissions granted by default) + grantUserPermissions([]); + setFolderAccessControl({}); + }); + + afterEach(() => { + config.buildInfo = originalBuildInfo; + }); + + describe('Basic Rendering', () => { + it('renders MoreButton correctly', async () => { + const mockRule = getGrafanaRule(); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + expect(await ui.moreButton.find()).toBeInTheDocument(); + }); + + it('opens menu when MoreButton is clicked', async () => { + const mockRule = getGrafanaRule(); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + + expect(ui.menu.query()).toBeInTheDocument(); + }); + + it('closes menu when clicking outside', async () => { + const mockRule = getGrafanaRule(); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(ui.menu.query()).toBeInTheDocument(); + + // Click outside the menu + await user.click(document.body); + + await waitFor(() => { + expect(ui.menu.query()).not.toBeInTheDocument(); + }); + }); + + it('menu contains expected structure', async () => { + const mockRule = getGrafanaRule({ + promRule: mockPromAlertingRule({ state: PromAlertingRuleState.Inactive }), + }); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + + const menuContents = await getMenuContents(); + expect(menuContents).toHaveLength(1); + expect(menuContents).toContain('Copy link'); + }); + }); + + describe('Permissions', () => { + // Generalized test to reduce repetition for menu item visibility + type MenuItemTestCase = { + description: string; + action: AlertRuleAction; + menuItem: keyof typeof ui.menuItems; + granted: boolean; + shouldShow: boolean; + }; + + const testMenuItemVisibility = ({ description, action, menuItem, granted, shouldShow }: MenuItemTestCase) => { + it(description, async () => { + const { mockRule, identifier, groupIdentifier } = createDefaultRuleSetup(); + + // Grant permissions based on action + const permissions: AccessControlAction[] = []; + const folderAccessControl: Record = {}; + + if (granted) { + switch (action) { + case AlertRuleAction.Pause: + case AlertRuleAction.Update: + permissions.push(AccessControlAction.AlertingRuleUpdate); + folderAccessControl[AccessControlAction.AlertingRuleUpdate] = true; + break; + case AlertRuleAction.Delete: + permissions.push(AccessControlAction.AlertingRuleDelete); + folderAccessControl[AccessControlAction.AlertingRuleDelete] = true; + break; + case AlertRuleAction.Duplicate: + permissions.push(AccessControlAction.AlertingRuleCreate); + break; + case AlertRuleAction.Silence: + permissions.push(AccessControlAction.AlertingInstanceCreate, AccessControlAction.AlertingSilenceCreate); + break; + case AlertRuleAction.ModifyExport: + permissions.push(AccessControlAction.AlertingRuleRead); + break; + } + } + + grantUserPermissions(permissions); + setFolderAccessControl(folderAccessControl); + + // Set up folder mock for Grafana rules with matching access control + mockFolderApi(server).folder( + 'namespace-uid', + mockFolder({ + uid: 'namespace-uid', + title: 'Test Folder', + accessControl: folderAccessControl, + }) + ); + + render( + + ); + + await openMenu(); + if (shouldShow) { + expect(await ui.menuItems[menuItem].find()).toBeInTheDocument(); + } else { + expect(ui.menuItems[menuItem].query()).not.toBeInTheDocument(); + } + }); + }; + + describe('Pause/Resume visibility', () => { + testMenuItemVisibility({ + description: 'shows Pause when pause permission is granted', + action: AlertRuleAction.Pause, + menuItem: 'pause', + granted: true, + shouldShow: true, + }); + + testMenuItemVisibility({ + description: 'hides Pause when pause permission is denied', + action: AlertRuleAction.Pause, + menuItem: 'pause', + granted: false, + shouldShow: false, + }); + }); + + describe('Delete visibility', () => { + testMenuItemVisibility({ + description: 'shows Delete when delete permission is granted', + action: AlertRuleAction.Delete, + menuItem: 'delete', + granted: true, + shouldShow: true, + }); + + testMenuItemVisibility({ + description: 'hides Delete when delete permission is denied', + action: AlertRuleAction.Delete, + menuItem: 'delete', + granted: false, + shouldShow: false, + }); + }); + + describe('Duplicate visibility', () => { + testMenuItemVisibility({ + description: 'shows Duplicate when duplicate permission is granted', + action: AlertRuleAction.Duplicate, + menuItem: 'duplicate', + granted: true, + shouldShow: true, + }); + + testMenuItemVisibility({ + description: 'hides Duplicate when duplicate permission is denied', + action: AlertRuleAction.Duplicate, + menuItem: 'duplicate', + granted: false, + shouldShow: false, + }); + }); + + describe('Silence visibility', () => { + testMenuItemVisibility({ + description: 'shows Silence when silence permission is granted', + action: AlertRuleAction.Silence, + menuItem: 'silence', + granted: true, + shouldShow: true, + }); + + testMenuItemVisibility({ + description: 'hides Silence when silence permission is denied', + action: AlertRuleAction.Silence, + menuItem: 'silence', + granted: false, + shouldShow: false, + }); + }); + + describe('Export visibility', () => { + testMenuItemVisibility({ + description: 'shows Export when export permission is granted', + action: AlertRuleAction.ModifyExport, + menuItem: 'export', + granted: true, + shouldShow: true, + }); + + testMenuItemVisibility({ + description: 'hides Export when export permission is denied', + action: AlertRuleAction.ModifyExport, + menuItem: 'export', + granted: false, + shouldShow: false, + }); + }); + + describe('Copy Link visibility', () => { + it('shows Copy Link when shareUrl exists', async () => { + const { mockRule, identifier, groupIdentifier } = createDefaultRuleSetup(); + + render( + + ); + + await openMenu(); + expect(await ui.menuItems.copyLink.find()).toBeInTheDocument(); + }); + }); + }); + + describe('Rule Types', () => { + beforeEach(() => { + // Grant all permissions for testing rule type differences + grantUserPermissions([ + AccessControlAction.AlertingRuleRead, + AccessControlAction.AlertingRuleUpdate, + AccessControlAction.AlertingRuleDelete, + AccessControlAction.AlertingRuleCreate, + AccessControlAction.AlertingInstanceCreate, + AccessControlAction.AlertingSilenceCreate, + ]); + setFolderAccessControl({ + [AccessControlAction.AlertingRuleUpdate]: true, + [AccessControlAction.AlertingRuleDelete]: true, + }); + mockFolderApi(server).folder( + 'namespace-uid', + mockFolder({ + uid: 'namespace-uid', + title: 'Test Folder', + accessControl: { + [AccessControlAction.AlertingRuleUpdate]: true, + [AccessControlAction.AlertingRuleDelete]: true, + }, + }) + ); + }); + + describe('Grafana-managed rules', () => { + it('shows Pause option for Grafana-managed alerting rules', async () => { + const mockRule = getGrafanaRule(); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(await ui.menuItems.pause.find()).toBeInTheDocument(); + }); + + it('does not show Pause option for datasource-managed rules', async () => { + const datasource = mockDataSource({ uid: 'mimir', name: 'Mimir' }); + const mockRule = getCloudRule({}, { rulesSource: datasource }); + const identifier = fromCombinedRule(datasource.name, mockRule); + const groupIdentifier = { + groupOrigin: 'datasource' as const, + rulesSource: { uid: datasource.uid, name: datasource.name, ruleSourceType: 'datasource' as const }, + namespace: { name: 'namespace-name' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(ui.menuItems.pause.query()).not.toBeInTheDocument(); + }); + }); + + describe('Alerting vs Recording rules', () => { + it('shows Silence option for alerting rules', async () => { + const mockRule = getGrafanaRule({ + promRule: mockPromAlertingRule({ type: PromRuleType.Alerting }), + }); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(await ui.menuItems.silence.find()).toBeInTheDocument(); + }); + + it('does not show Silence option for recording rules', async () => { + const mockRule = getGrafanaRule({ + promRule: mockPromRecordingRule({ type: PromRuleType.Recording }), + }); + // Override the rulerRule to be a recording rule + mockRule.rulerRule = mockRulerGrafanaRecordingRule( + {}, + { + uid: 'mock-rule-uid-123', + namespace_uid: 'namespace-uid', + } + ); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + mockFolderApi(server).folder( + 'namespace-uid', + mockFolder({ + uid: 'namespace-uid', + title: 'Test Folder', + }) + ); + + render( + + ); + + await openMenu(); + expect(ui.menuItems.silence.query()).not.toBeInTheDocument(); + }); + }); + + describe('Provisioned rules', () => { + it('hides Delete option for provisioned rules', async () => { + const mockRule = getGrafanaRule({ + rulerRule: mockGrafanaRulerRule({ provenance: 'file' }), + }); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(ui.menuItems.delete.query()).not.toBeInTheDocument(); + }); + + it('hides Pause option for provisioned rules', async () => { + const mockRule = getGrafanaRule({ + rulerRule: mockGrafanaRulerRule({ provenance: 'file' }), + }); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(ui.menuItems.pause.query()).not.toBeInTheDocument(); + }); + }); + + describe('Mixed data scenarios', () => { + it('works with only promRule available', async () => { + const mockRule = getGrafanaRule(); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(ui.menu.query()).toBeInTheDocument(); + }); + + it('works with only rulerRule available', async () => { + const mockRule = getGrafanaRule(); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(ui.menu.query()).toBeInTheDocument(); + }); + + it('works with both promRule and rulerRule available', async () => { + const mockRule = getGrafanaRule(); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(ui.menu.query()).toBeInTheDocument(); + }); + }); + }); + + describe('Handler Callbacks', () => { + describe('handleSilence', () => { + it('calls handleSilence when Silence menu item is clicked', async () => { + grantUserPermissions([AccessControlAction.AlertingInstanceCreate, AccessControlAction.AlertingSilenceCreate]); + mockFolderApi(server).folder('namespace-uid', mockFolder({ uid: 'namespace-uid', title: 'Test Folder' })); + + const { mockRule, identifier, groupIdentifier } = createDefaultRuleSetup(); + + render( + + ); + + await openMenu(); + await user.click(await ui.menuItems.silence.find()); + + expect(handleSilence).toHaveBeenCalledTimes(1); + }); + }); + + describe('handleManageEnrichments', () => { + testWithFeatureToggles({ + enable: ['alertEnrichment', 'alertingEnrichmentPerRule'], + }); + + it('calls handleManageEnrichments when Manage enrichments menu item is clicked', async () => { + // Both toggles need to be enabled: alertEnrichment for the hook, alertingEnrichmentPerRule for the component + grantUserPermissions([AccessControlAction.AlertingEnrichmentsRead]); + mockFolderApi(server).folder('namespace-uid', mockFolder({ uid: 'namespace-uid', title: 'Test Folder' })); + + const handleManageEnrichments = jest.fn(); + const { mockRule, identifier, groupIdentifier } = createDefaultRuleSetup(); + + render( + + ); + + await openMenu(); + await user.click(await ui.menuItems.manageEnrichments.find()); + + expect(handleManageEnrichments).toHaveBeenCalledTimes(1); + }); + }); + + describe('handleDelete', () => { + it('calls handleDelete with correct identifier and groupIdentifier when Delete menu item is clicked', async () => { + grantUserPermissions([AccessControlAction.AlertingRuleDelete]); + setFolderAccessControl({ [AccessControlAction.AlertingRuleDelete]: true }); + mockFolderApi(server).folder( + 'namespace-uid', + mockFolder({ + uid: 'namespace-uid', + title: 'Test Folder', + accessControl: { [AccessControlAction.AlertingRuleDelete]: true }, + }) + ); + + const { mockRule, identifier, groupIdentifier } = createDefaultRuleSetup(); + + render( + + ); + + await openMenu(); + await user.click(await ui.menuItems.delete.find()); + + expect(handleDelete).toHaveBeenCalledTimes(1); + expect(handleDelete).toHaveBeenCalledWith(identifier, groupIdentifier); + }); + + it('does not call handleDelete when identifier is not editable', async () => { + grantUserPermissions([AccessControlAction.AlertingRuleDelete]); + setFolderAccessControl({ [AccessControlAction.AlertingRuleDelete]: true }); + mockFolderApi(server).folder( + 'namespace-uid', + mockFolder({ + uid: 'namespace-uid', + title: 'Test Folder', + accessControl: { [AccessControlAction.AlertingRuleDelete]: true }, + }) + ); + + const { mockRule, groupIdentifier } = createDefaultRuleSetup(); + // Create a non-editable identifier (e.g., Prometheus rule identifier without rulerRule) + // For external rules without rulerRule, the delete button should not appear + const identifier = fromCombinedRule('prometheus', { + ...mockRule, + rulerRule: undefined, + }); + + render( + + ); + + await openMenu(); + expect(ui.menuItems.delete.query()).not.toBeInTheDocument(); + expect(handleDelete).not.toHaveBeenCalled(); + }); + }); + + describe('handleDuplicateRule', () => { + it('calls handleDuplicateRule with correct identifier when Duplicate menu item is clicked', async () => { + grantUserPermissions([AccessControlAction.AlertingRuleCreate]); + mockFolderApi(server).folder('namespace-uid', mockFolder({ uid: 'namespace-uid', title: 'Test Folder' })); + + const { mockRule, identifier, groupIdentifier } = createDefaultRuleSetup(); + + render( + + ); + + await openMenu(); + await user.click(await ui.menuItems.duplicate.find()); + + expect(handleDuplicateRule).toHaveBeenCalledTimes(1); + expect(handleDuplicateRule).toHaveBeenCalledWith(identifier); + }); + }); + + describe('onPauseChange', () => { + it('calls onPauseChange after pause state change when Pause menu item is clicked', async () => { + const onPauseChange = jest.fn(); + grantUserPermissions([AccessControlAction.AlertingRuleUpdate]); + setFolderAccessControl({ [AccessControlAction.AlertingRuleUpdate]: true }); + mockFolderApi(server).folder( + 'namespace-uid', + mockFolder({ + uid: 'namespace-uid', + title: 'Test Folder', + accessControl: { [AccessControlAction.AlertingRuleUpdate]: true }, + }) + ); + + const { mockRule, identifier, groupIdentifier } = createDefaultRuleSetup(); + + render( + + ); + + await openMenu(); + await user.click(await ui.menuItems.pause.find()); + + await waitFor(() => { + expect(mockPauseExecute).toHaveBeenCalledTimes(1); + }); + + await waitFor(() => { + expect(onPauseChange).toHaveBeenCalledTimes(1); + }); + expect(onPauseChange).toHaveBeenCalledWith(); + }); + + it('calls onPauseChange after resume state change when Resume menu item is clicked', async () => { + const onPauseChange = jest.fn(); + grantUserPermissions([AccessControlAction.AlertingRuleUpdate]); + setFolderAccessControl({ [AccessControlAction.AlertingRuleUpdate]: true }); + mockFolderApi(server).folder( + 'namespace-uid', + mockFolder({ + uid: 'namespace-uid', + title: 'Test Folder', + accessControl: { [AccessControlAction.AlertingRuleUpdate]: true }, + }) + ); + + const mockRule = getGrafanaRule({ + rulerRule: mockGrafanaRulerRule({ is_paused: true }), + }); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + await user.click(await ui.menuItems.resume.find()); + + await waitFor(() => { + expect(mockPauseExecute).toHaveBeenCalledTimes(1); + }); + + await waitFor(() => { + expect(onPauseChange).toHaveBeenCalledTimes(1); + }); + expect(onPauseChange).toHaveBeenCalledWith(); + }); + }); + }); + + describe('Feature Flags', () => { + describe('alertingEnrichmentPerRule', () => { + describe('when feature flag is disabled', () => { + testWithFeatureToggles({ + disable: ['alertingEnrichmentPerRule'], + }); + + it('hides Manage enrichments when feature flag is disabled', async () => { + grantUserPermissions([AccessControlAction.AlertingEnrichmentsRead]); + + const handleManageEnrichments = jest.fn(); + const { mockRule, identifier, groupIdentifier } = createDefaultRuleSetup(); + + render( + + ); + + await openMenu(); + expect(ui.menuItems.manageEnrichments.query()).not.toBeInTheDocument(); + }); + }); + + describe('when feature flag is enabled', () => { + testWithFeatureToggles({ + enable: ['alertEnrichment', 'alertingEnrichmentPerRule'], + }); + + it('shows Manage enrichments when feature flag is enabled and all conditions are met', async () => { + // Both toggles need to be enabled: alertEnrichment for the hook, alertingEnrichmentPerRule for the component + grantUserPermissions([AccessControlAction.AlertingEnrichmentsRead]); + mockFolderApi(server).folder('namespace-uid', mockFolder({ uid: 'namespace-uid', title: 'Test Folder' })); + + const handleManageEnrichments = jest.fn(); + const { mockRule, identifier, groupIdentifier } = createDefaultRuleSetup(); + + render( + + ); + + await openMenu(); + expect(await ui.menuItems.manageEnrichments.find()).toBeInTheDocument(); + }); + }); + + describe('when feature flags are enabled but permission is missing', () => { + testWithFeatureToggles({ + enable: ['alertEnrichment', 'alertingEnrichmentPerRule'], + }); + + it('hides Manage enrichments when enrichment ability is not allowed', async () => { + // Enable both toggles to ensure we're testing the permission check, not the toggles + grantUserPermissions([]); + mockFolderApi(server).folder('namespace-uid', mockFolder({ uid: 'namespace-uid', title: 'Test Folder' })); + + const handleManageEnrichments = jest.fn(); + const { mockRule, identifier, groupIdentifier } = createDefaultRuleSetup(); + + render( + + ); + + await openMenu(); + expect(ui.menuItems.manageEnrichments.query()).not.toBeInTheDocument(); + }); + }); + }); + + describe('Open-source vs Enterprise', () => { + it('hides Declare Incident in open-source edition for firing alerting rules', async () => { + config.buildInfo.edition = GrafanaEdition.OpenSource; + config.buildInfo.env = 'production'; + + const mockRule = getGrafanaRule({ + promRule: mockPromAlertingRule({ state: PromAlertingRuleState.Firing }), + }); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(ui.menuItems.declareIncident.query()).not.toBeInTheDocument(); + }); + + it('shows Declare Incident in enterprise edition for firing alerting rules', async () => { + config.buildInfo.edition = GrafanaEdition.Enterprise; + config.buildInfo.env = 'production'; + + const mockRule = getGrafanaRule({ + promRule: mockPromAlertingRule({ state: PromAlertingRuleState.Firing }), + }); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(await ui.menuItems.declareIncident.find()).toBeInTheDocument(); + }); + + it('shows Declare Incident in dev mode even for open-source edition', async () => { + config.buildInfo.edition = GrafanaEdition.OpenSource; + config.buildInfo.env = 'development'; + + const mockRule = getGrafanaRule({ + promRule: mockPromAlertingRule({ state: PromAlertingRuleState.Firing }), + }); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(await ui.menuItems.declareIncident.find()).toBeInTheDocument(); + }); + + it('hides Declare Incident for non-firing alerting rules in enterprise', async () => { + config.buildInfo.edition = GrafanaEdition.Enterprise; + config.buildInfo.env = 'production'; + + const mockRule = getGrafanaRule({ + promRule: mockPromAlertingRule({ state: PromAlertingRuleState.Inactive }), + }); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(ui.menuItems.declareIncident.query()).not.toBeInTheDocument(); + }); + + it('hides Declare Incident for recording rules', async () => { + config.buildInfo.edition = GrafanaEdition.Enterprise; + config.buildInfo.env = 'production'; + + const mockRule = getGrafanaRule({ + promRule: mockPromRecordingRule({ type: PromRuleType.Recording }), + }); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(ui.menuItems.declareIncident.query()).not.toBeInTheDocument(); + }); + }); + + describe('Assistant availability', () => { + beforeEach(() => { + // Reset config to ensure clean state for assistant tests + config.buildInfo = { ...originalBuildInfo }; + config.buildInfo.env = 'production'; + config.buildInfo.edition = GrafanaEdition.OpenSource; + // Reset assistant mock to default (unavailable) for each test + mockUseAssistant.mockReturnValue({ + isAvailable: false, + openAssistant: mockOpenAssistant, + } as unknown as ReturnType); + }); + + it('shows Analyze Rule when assistant is available for Grafana-managed rules', async () => { + // Override mock to return available + mockUseAssistant.mockReturnValue({ + isAvailable: true, + openAssistant: mockOpenAssistant, + } as unknown as ReturnType); + + // Create a Grafana rule with promRule that has uid and folderUid + const mockRule = getGrafanaRule({ + promRule: mockPromAlertingRule({ + uid: 'test-rule-uid', + folderUid: 'test-folder-uid', + }), + }); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(await ui.menuItems.analyzeRule.find()).toBeInTheDocument(); + }); + + it('hides Analyze Rule when assistant is unavailable', async () => { + // Mock already set to unavailable in beforeEach, but be explicit + mockUseAssistant.mockReturnValue({ + isAvailable: false, + openAssistant: mockOpenAssistant, + } as unknown as ReturnType); + + const { mockRule, identifier, groupIdentifier } = createDefaultRuleSetup(); + + render( + + ); + + await openMenu(); + expect(ui.menuItems.analyzeRule.query()).not.toBeInTheDocument(); + }); + + it('hides Analyze Rule for datasource-managed rules even when assistant is available', async () => { + mockUseAssistant.mockReturnValue({ + isAvailable: true, + openAssistant: mockOpenAssistant, + } as unknown as ReturnType); + + const datasource = mockDataSource({ uid: 'mimir', name: 'Mimir' }); + const mockRule = getCloudRule({}, { rulesSource: datasource }); + const identifier = fromCombinedRule(datasource.name, mockRule); + const groupIdentifier = { + groupOrigin: 'datasource' as const, + rulesSource: { uid: datasource.uid, name: datasource.name, ruleSourceType: 'datasource' as const }, + namespace: { name: 'namespace-name' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(ui.menuItems.analyzeRule.query()).not.toBeInTheDocument(); + }); + }); + }); + + describe('Edge Cases', () => { + describe('Missing Data', () => { + describe('with enrichment feature flags enabled', () => { + testWithFeatureToggles({ + enable: ['alertEnrichment', 'alertingEnrichmentPerRule'], + }); + + it('hides Manage enrichments when ruleUid is missing even if all other conditions are met', async () => { + grantUserPermissions([AccessControlAction.AlertingEnrichmentsRead]); + // Note: Cloud rules typically don't have a ruleUid, so this tests that case + mockFolderApi(server).folder('namespace-uid', mockFolder({ uid: 'namespace-uid', title: 'Test Folder' })); + + const handleManageEnrichments = jest.fn(); + const datasource = mockDataSource({ uid: 'prometheus', name: 'Prometheus' }); + const mockRule = getCloudRule({}, { rulesSource: datasource }); + const identifier = fromCombinedRule(datasource.name, mockRule); + const groupIdentifier = { + groupOrigin: 'datasource' as const, + rulesSource: { uid: datasource.uid, name: datasource.name, ruleSourceType: 'datasource' as const }, + namespace: { name: 'namespace-name' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(ui.menuItems.manageEnrichments.query()).not.toBeInTheDocument(); + }); + }); + + it('hides Pause option when ruleUid is missing even if pause permission is granted', async () => { + grantUserPermissions([AccessControlAction.AlertingRuleUpdate]); + setFolderAccessControl({ [AccessControlAction.AlertingRuleUpdate]: true }); + + const datasource = mockDataSource({ uid: 'prometheus', name: 'Prometheus' }); + const mockRule = getCloudRule({}, { rulesSource: datasource }); + const identifier = fromCombinedRule(datasource.name, mockRule); + const groupIdentifier = { + groupOrigin: 'datasource' as const, + rulesSource: { uid: datasource.uid, name: datasource.name, ruleSourceType: 'datasource' as const }, + namespace: { name: 'namespace-name' }, + groupName: 'group-name', + }; + + render( + + ); + + await openMenu(); + expect(ui.menuItems.pause.query()).not.toBeInTheDocument(); + }); + + it('pause still works when onPauseChange is not provided', async () => { + grantUserPermissions([AccessControlAction.AlertingRuleUpdate]); + setFolderAccessControl({ [AccessControlAction.AlertingRuleUpdate]: true }); + mockFolderApi(server).folder( + 'namespace-uid', + mockFolder({ + uid: 'namespace-uid', + title: 'Test Folder', + accessControl: { [AccessControlAction.AlertingRuleUpdate]: true }, + }) + ); + + const { mockRule, identifier, groupIdentifier } = createDefaultRuleSetup(); + + render( + + ); + + await openMenu(); + await user.click(await ui.menuItems.pause.find()); + + // Pause should still execute even without callback + await waitFor(() => { + expect(mockPauseExecute).toHaveBeenCalledTimes(1); + }); + }); + }); + + describe('Empty States', () => { + it('shows minimal menu with only Copy Link when no permissions are granted', async () => { + // Use a non-firing rule to avoid Declare Incident showing + const mockRule = getGrafanaRule({ + promRule: mockPromAlertingRule({ state: PromAlertingRuleState.Inactive }), + }); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + config.buildInfo.edition = GrafanaEdition.OpenSource; + config.buildInfo.env = 'production'; + + render( + + ); + + await openMenu(); + + expect(await ui.menuItems.copyLink.find()).toBeInTheDocument(); + expect(ui.menuItems.pause.query()).not.toBeInTheDocument(); + expect(ui.menuItems.silence.query()).not.toBeInTheDocument(); + expect(ui.menuItems.duplicate.query()).not.toBeInTheDocument(); + expect(ui.menuItems.export.query()).not.toBeInTheDocument(); + expect(ui.menuItems.delete.query()).not.toBeInTheDocument(); + expect(ui.menuItems.manageEnrichments.query()).not.toBeInTheDocument(); + expect(ui.menuItems.declareIncident.query()).not.toBeInTheDocument(); + expect(ui.menuItems.analyzeRule.query()).not.toBeInTheDocument(); + }); + + it('menu still opens even when no applicable items are available', async () => { + // All abilities denied and no shareUrl + const mockRule = getGrafanaRule({ + promRule: mockPromAlertingRule({ state: PromAlertingRuleState.Inactive }), + }); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + config.buildInfo.edition = GrafanaEdition.OpenSource; + config.buildInfo.env = 'production'; + + // Mock createShareLink to return undefined + const createShareLinkSpy = jest.spyOn(miscUtils, 'createShareLink'); + createShareLinkSpy.mockReturnValue(undefined); + + render( + + ); + + await openMenu(); + + expect(ui.menu.query()).toBeInTheDocument(); + const menuItems = screen.queryAllByRole('menuitem'); + const linkItems = screen.queryAllByRole('link'); + expect(menuItems.length).toBe(0); + expect(linkItems.length).toBe(0); + + createShareLinkSpy.mockRestore(); + }); + }); + + describe('Error Handling', () => { + it('handles gracefully when clipboard API is unavailable', async () => { + const originalClipboard = navigator.clipboard; + // Mock clipboard as undefined to simulate unavailable API + Object.defineProperty(navigator, 'clipboard', { + value: undefined, + writable: true, + configurable: true, + }); + + const mockRule = getGrafanaRule({ + promRule: mockPromAlertingRule({ state: PromAlertingRuleState.Inactive }), + }); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + config.buildInfo.edition = GrafanaEdition.OpenSource; + config.buildInfo.env = 'production'; + + render( + + ); + + await openMenu(); + const copyLinkItem = await ui.menuItems.copyLink.find(); + + await expect(user.click(copyLinkItem)).resolves.not.toThrow(); + + // Restore clipboard + Object.defineProperty(navigator, 'clipboard', { + value: originalClipboard, + writable: true, + configurable: true, + }); + }); + + it('handles gracefully when shareUrl is undefined', async () => { + // Use a non-firing rule to avoid Declare Incident showing + const mockRule = getGrafanaRule({ + promRule: mockPromAlertingRule({ state: PromAlertingRuleState.Inactive }), + }); + const identifier = fromCombinedRule('grafana', mockRule); + const groupIdentifier = { + groupOrigin: 'grafana' as const, + namespace: { uid: 'namespace-uid' }, + groupName: 'group-name', + }; + + config.buildInfo.edition = GrafanaEdition.OpenSource; + config.buildInfo.env = 'production'; + + // Mock createShareLink to return undefined + const createShareLinkSpy = jest.spyOn(require('app/features/alerting/unified/utils/misc'), 'createShareLink'); + createShareLinkSpy.mockReturnValue(undefined); + + render( + + ); + + await openMenu(); + + expect(ui.menuItems.copyLink.query()).not.toBeInTheDocument(); + expect(ui.menu.query()).toBeInTheDocument(); + + createShareLinkSpy.mockRestore(); + }); + }); + }); +}); From 49032ae3d7a55a62563fcfcc6d45bb69b82b2159 Mon Sep 17 00:00:00 2001 From: Adela Almasan <88068998+adela-almasan@users.noreply.github.com> Date: Fri, 19 Dec 2025 11:23:27 -0600 Subject: [PATCH 265/297] VizSuggestions: Update selected suggestion styling (#115581) * update selected suggestion style * update highlight styles for light theme, add inert to div * remove commented-out original idea --------- Co-authored-by: Paul Marbach --- .../VisualizationSuggestionCard.tsx | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx index 7248dbcc1e5..632c143fe3a 100644 --- a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx +++ b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestionCard.tsx @@ -2,7 +2,7 @@ import { css, cx } from '@emotion/css'; import { cloneDeep } from 'lodash'; import { CSSProperties, HTMLAttributes, ReactNode } from 'react'; -import { colorManipulator, GrafanaTheme2, PanelData, PanelPluginVisualizationSuggestion } from '@grafana/data'; +import { GrafanaTheme2, PanelData, PanelPluginVisualizationSuggestion } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { config } from '@grafana/runtime'; import { Tooltip, useStyles2 } from '@grafana/ui'; @@ -31,7 +31,11 @@ export function VisualizationSuggestionCard({ const commonButtonProps = { 'aria-label': suggestion.name, - className: cx(className, styles.vizBox), + className: cx( + className, + styles.vizBox, + config.featureToggles.newVizSuggestions && isSelected && styles.selectedBox + ), 'data-testid': selectors.components.VisualizationPreview.card(suggestion.name), style: outerStyles, tabIndex: -1, // selection is handled by parent container @@ -56,7 +60,12 @@ export function VisualizationSuggestionCard({ content = ( ); @@ -82,22 +89,8 @@ export function VisualizationSuggestionCard({ const getStyles = (theme: GrafanaTheme2) => { return { - hoverPane: css({ - position: 'absolute', - top: -4, - left: -4, - right: -2, - bottom: -2, - borderRadius: theme.spacing(0.5), - background: 'transparent', - [theme.transitions.handleMotion('no-preference', 'reduce')]: { - transition: theme.transitions.create(['background'], { - duration: theme.transitions.duration.short, - }), - }, - }), - hoverPaneSelected: css({ - background: colorManipulator.alpha(theme.colors.text.primary, 0.1), + selectedSuggestion: css({ + filter: `blur(1px) ${theme.isDark ? 'brightness(0.5)' : 'opacity(0.3)'}`, }), vizBox: css({ position: 'relative', @@ -149,6 +142,10 @@ const getStyles = (theme: GrafanaTheme2) => { top: '6px', left: '6px', }), + selectedBox: css({ + border: `1px solid ${theme.colors.primary.border}`, + background: theme.colors.action.selected, + }), }; }; From f91efcfe2c8f6e6078d64118e3fd1332f9c12873 Mon Sep 17 00:00:00 2001 From: Jesse David Peterson Date: Fri, 19 Dec 2025 13:12:01 -0500 Subject: [PATCH 266/297] TimeSeries: Fix truncated label text in legend table mode (#115647) * fix(legend-table): remove arbitrary 600px max width for full width cells * test(legend-table): backfill test coverage for viz legend table * test(legend-table): backfill test coverage for viz legend table item * refactor(legend-table): use derived theme spacing, not hard-coded values --- .../VizLegend/VizLegendTable.test.tsx | 78 ++++++++++++ .../components/VizLegend/VizLegendTable.tsx | 1 - .../VizLegend/VizLegendTableItem.test.tsx | 112 ++++++++++++++++++ .../VizLegend/VizLegendTableItem.tsx | 64 ++++++---- 4 files changed, 232 insertions(+), 23 deletions(-) create mode 100644 packages/grafana-ui/src/components/VizLegend/VizLegendTable.test.tsx create mode 100644 packages/grafana-ui/src/components/VizLegend/VizLegendTableItem.test.tsx diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendTable.test.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendTable.test.tsx new file mode 100644 index 00000000000..131133bcdfb --- /dev/null +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendTable.test.tsx @@ -0,0 +1,78 @@ +import { render, screen } from '@testing-library/react'; + +import { VizLegendTable } from './VizLegendTable'; +import { VizLegendItem } from './types'; + +describe('VizLegendTable', () => { + const mockItems: VizLegendItem[] = [ + { label: 'Series 1', color: 'red', yAxis: 1 }, + { label: 'Series 2', color: 'blue', yAxis: 1 }, + { label: 'Series 3', color: 'green', yAxis: 1 }, + ]; + + it('renders without crashing', () => { + const { container } = render(); + expect(container.querySelector('table')).toBeInTheDocument(); + }); + + it('renders all items', () => { + render(); + expect(screen.getByText('Series 1')).toBeInTheDocument(); + expect(screen.getByText('Series 2')).toBeInTheDocument(); + expect(screen.getByText('Series 3')).toBeInTheDocument(); + }); + + it('renders table headers when items have display values', () => { + const itemsWithStats: VizLegendItem[] = [ + { + label: 'Series 1', + color: 'red', + yAxis: 1, + getDisplayValues: () => [ + { numeric: 100, text: '100', title: 'Max' }, + { numeric: 50, text: '50', title: 'Min' }, + ], + }, + ]; + render(); + expect(screen.getByText('Max')).toBeInTheDocument(); + expect(screen.getByText('Min')).toBeInTheDocument(); + }); + + it('renders sort icon when sorted', () => { + const { container } = render( + + ); + expect(container.querySelector('svg')).toBeInTheDocument(); + }); + + it('calls onToggleSort when header is clicked', () => { + const onToggleSort = jest.fn(); + render(); + const header = screen.getByText('Name'); + header.click(); + expect(onToggleSort).toHaveBeenCalledWith('Name'); + }); + + it('does not call onToggleSort when not sortable', () => { + const onToggleSort = jest.fn(); + render(); + const header = screen.getByText('Name'); + header.click(); + expect(onToggleSort).not.toHaveBeenCalled(); + }); + + it('renders with long labels', () => { + const itemsWithLongLabels: VizLegendItem[] = [ + { + label: 'This is a very long series name that should be scrollable within its table cell', + color: 'red', + yAxis: 1, + }, + ]; + render(); + expect( + screen.getByText('This is a very long series name that should be scrollable within its table cell') + ).toBeInTheDocument(); + }); +}); diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendTable.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendTable.tsx index b654a2d3ac6..0c2859453eb 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendTable.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendTable.tsx @@ -119,7 +119,6 @@ const getStyles = (theme: GrafanaTheme2) => ({ table: css({ width: '100%', 'th:first-child': { - width: '100%', borderBottom: `1px solid ${theme.colors.border.weak}`, }, }), diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendTableItem.test.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendTableItem.test.tsx new file mode 100644 index 00000000000..4ca95aa395c --- /dev/null +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendTableItem.test.tsx @@ -0,0 +1,112 @@ +import { render, screen } from '@testing-library/react'; + +import { LegendTableItem } from './VizLegendTableItem'; +import { VizLegendItem } from './types'; + +describe('LegendTableItem', () => { + const mockItem: VizLegendItem = { + label: 'Series 1', + color: 'red', + yAxis: 1, + }; + + it('renders without crashing', () => { + const { container } = render( +
+ + + +
+ ); + expect(container.querySelector('tr')).toBeInTheDocument(); + }); + + it('renders label text', () => { + render( + + + + +
+ ); + expect(screen.getByText('Series 1')).toBeInTheDocument(); + }); + + it('renders with long label text', () => { + const longLabelItem: VizLegendItem = { + ...mockItem, + label: 'This is a very long series name that should be scrollable in the table cell', + }; + render( + + + + +
+ ); + expect( + screen.getByText('This is a very long series name that should be scrollable in the table cell') + ).toBeInTheDocument(); + }); + + it('renders stat values when provided', () => { + const itemWithStats: VizLegendItem = { + ...mockItem, + getDisplayValues: () => [ + { numeric: 100, text: '100', title: 'Max' }, + { numeric: 50, text: '50', title: 'Min' }, + ], + }; + render( + + + + +
+ ); + expect(screen.getByText('100')).toBeInTheDocument(); + expect(screen.getByText('50')).toBeInTheDocument(); + }); + + it('renders right y-axis indicator when yAxis is 2', () => { + const rightAxisItem: VizLegendItem = { + ...mockItem, + yAxis: 2, + }; + render( + + + + +
+ ); + expect(screen.getByText('(right y-axis)')).toBeInTheDocument(); + }); + + it('calls onLabelClick when label is clicked', () => { + const onLabelClick = jest.fn(); + render( + + + + +
+ ); + const button = screen.getByRole('button'); + button.click(); + expect(onLabelClick).toHaveBeenCalledWith(mockItem, expect.any(Object)); + }); + + it('does not call onClick when readonly', () => { + const onLabelClick = jest.fn(); + render( + + + + +
+ ); + const button = screen.getByRole('button'); + expect(button).toBeDisabled(); + }); +}); diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegendTableItem.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegendTableItem.tsx index 335cf4309e9..56ec6cb733e 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegendTableItem.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegendTableItem.tsx @@ -69,7 +69,7 @@ export const LegendTableItem = ({ return ( - + - +
+ +
{item.getDisplayValues && @@ -128,6 +130,27 @@ const getStyles = (theme: GrafanaTheme2) => { background: rowHoverBg, }, }), + labelCell: css({ + label: 'LegendLabelCell', + maxWidth: 0, + width: '100%', + }), + labelCellInner: css({ + label: 'LegendLabelCellInner', + display: 'block', + flex: 1, + minWidth: 0, + overflowX: 'auto', + overflowY: 'hidden', + paddingRight: theme.spacing(3), + scrollbarWidth: 'none', + msOverflowStyle: 'none', + maskImage: `linear-gradient(to right, black calc(100% - ${theme.spacing(3)}), transparent 100%)`, + WebkitMaskImage: `linear-gradient(to right, black calc(100% - ${theme.spacing(3)}), transparent 100%)`, + '&::-webkit-scrollbar': { + display: 'none', + }, + }), label: css({ label: 'LegendLabel', whiteSpace: 'nowrap', @@ -135,9 +158,6 @@ const getStyles = (theme: GrafanaTheme2) => { border: 'none', fontSize: 'inherit', padding: 0, - maxWidth: '600px', - textOverflow: 'ellipsis', - overflow: 'hidden', userSelect: 'text', }), labelDisabled: css({ From 471d6f5236b6521b09a0c5d95c7e6f33764a4865 Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:27:39 -0500 Subject: [PATCH 267/297] Docs: Add suggested dashboards (#114729) --- docs/sources/datasources/_index.md | 6 +++++ .../create-dashboard/index.md | 24 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/sources/datasources/_index.md b/docs/sources/datasources/_index.md index e7da61ea2f5..28d46c9c600 100644 --- a/docs/sources/datasources/_index.md +++ b/docs/sources/datasources/_index.md @@ -112,6 +112,12 @@ For example, this video demonstrates the visual Prometheus query builder: For general information about querying in Grafana, and common options and user interface elements across all query editors, refer to [Query and transform data](ref:query-transform-data). +## Build a dashboard from the data source + +After you've configured a data source, you can start creating a dashboard directly from it, by clicking the **Build a dashboard** button. + +For more information, refer to [Begin dashboard creation from data source configuration](https://grafana.com/docs/grafana//visualizations/dashboards/build-dashboards/create-dashboard/#begin-dashboard-creation-from-connections). + ## Special data sources Grafana includes three special data sources: diff --git a/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md b/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md index 3d5e765e0cd..10196a40811 100644 --- a/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md +++ b/docs/sources/visualizations/dashboards/build-dashboards/create-dashboard/index.md @@ -99,7 +99,7 @@ Dashboards and panels allow you to show your data in visual form. Each panel nee - Understand the query language of the target data source. - Ensure that data source for which you are writing a query has been added. For more information about adding a data source, refer to [Add a data source](ref:add-a-data-source) if you need instructions. -**To create a dashboard**: +To create a dashboard, follow these steps: {{< shared id="create-dashboard" >}} @@ -171,6 +171,28 @@ Dashboards and panels allow you to show your data in visual form. Each panel nee Now, when you want to make more changes to the saved dashboard, click **Edit** in the top-right corner. +### Begin dashboard creation from data source configuration + +You can start the process of creating a dashboard directly from a data source rather than from the **Dashboards** page. + +To begin building a dashboard directly from a data source, follow these steps: + +1. Navigate to **Connections > Data sources**. +1. On the row of the data source for which you want to build a dashboard, click **Build a dashboard**. + + The empty dashboard page opens. + +1. Do one of the following: + - Click **+Add visualization** to configure all the elements of the new dashboard. + - Select one of the suggested dashboards by clicking its **Use dashboard** button. This can be helpful when you're not sure how to most effectively visualize your data. + The suggested dashboards are specific to your data source type (for example, Prometheus, Loki, or Elasticsearch). If there are more than three dashboard suggestions, you can click **View all** to see the rest of them. + + ![Empty dashboard with add visualization and suggested dashboard options](/media/docs/grafana/dashboards/screenshot-suggested-dashboards-v12.3.png) + + {{< docs/public-preview product="Suggested dashboards" >}} + +1. Complete the rest of the dashboard configuration. For more detailed steps, refer to [Create a dashboard](#create-a-dashboard), beginning at step five. + ## Copy a dashboard To copy a dashboard, follow these steps: From 14c595f2066151ad5b9a4f1d852f60eac63a8b29 Mon Sep 17 00:00:00 2001 From: Liza Detrick <114438185+L2D2Grafana@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:52:02 -0800 Subject: [PATCH 268/297] Logs: Cell format value on inspect should use Code view for arrays, objects, and JSON strings (#115037) --- .../src/components/Table/CellActions.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/Table/CellActions.tsx b/packages/grafana-ui/src/components/Table/CellActions.tsx index e2fe8a658ba..f21be659c4f 100644 --- a/packages/grafana-ui/src/components/Table/CellActions.tsx +++ b/packages/grafana-ui/src/components/Table/CellActions.tsx @@ -1,3 +1,4 @@ +import { isPlainObject } from 'lodash'; import { useCallback } from 'react'; import * as React from 'react'; @@ -63,7 +64,18 @@ export function CellActions({ tooltip={t('grafana-ui.table.cell-inspect', 'Inspect value')} onClick={() => { if (setInspectCell) { - setInspectCell({ value: cell.value, mode: previewMode }); + let mode = TableCellInspectorMode.text; + let inspectValue = cell.value; + try { + const parsed = typeof inspectValue === 'string' ? JSON.parse(inspectValue) : inspectValue; + if (Array.isArray(parsed) || isPlainObject(parsed)) { + inspectValue = JSON.stringify(parsed, null, 2); + mode = TableCellInspectorMode.code; + } + } catch { + // do nothing + } + setInspectCell({ value: inspectValue, mode }); } }} {...commonButtonProps} From 4164239f561e70669549ef376bea20b39c638f6e Mon Sep 17 00:00:00 2001 From: Will Assis <35489495+gassiss@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:27:06 -0500 Subject: [PATCH 269/297] unified-storage: implement sqlkv Save method (#115458) * unified-storage: sqlkv save method --- .../data/sqlkv_delete_legacy_resource.sql | 5 + .../resource/data/sqlkv_insert_datastore.sql | 21 ++ .../data/sqlkv_insert_legacy_resource.sql | 31 +++ .../sqlkv_insert_legacy_resource_history.sql | 44 ++++ .../resource/data/sqlkv_save_event.sql | 15 ++ .../resource/data/sqlkv_update_datastore.sql | 3 + .../data/sqlkv_update_legacy_resource.sql | 9 + pkg/storage/unified/resource/datastore.go | 44 +++- pkg/storage/unified/resource/eventstore.go | 1 + pkg/storage/unified/resource/sqlkv.go | 249 ++++++++++++++++-- .../unified/resource/storage_backend.go | 59 ++++- .../unified/sql/rvmanager/rv_manager.go | 27 +- pkg/storage/unified/sql/server.go | 27 +- pkg/storage/unified/testing/kv.go | 45 ++-- pkg/storage/unified/testing/kv_test.go | 1 - 15 files changed, 534 insertions(+), 47 deletions(-) create mode 100644 pkg/storage/unified/resource/data/sqlkv_delete_legacy_resource.sql create mode 100644 pkg/storage/unified/resource/data/sqlkv_insert_datastore.sql create mode 100644 pkg/storage/unified/resource/data/sqlkv_insert_legacy_resource.sql create mode 100644 pkg/storage/unified/resource/data/sqlkv_insert_legacy_resource_history.sql create mode 100644 pkg/storage/unified/resource/data/sqlkv_save_event.sql create mode 100644 pkg/storage/unified/resource/data/sqlkv_update_datastore.sql create mode 100644 pkg/storage/unified/resource/data/sqlkv_update_legacy_resource.sql diff --git a/pkg/storage/unified/resource/data/sqlkv_delete_legacy_resource.sql b/pkg/storage/unified/resource/data/sqlkv_delete_legacy_resource.sql new file mode 100644 index 00000000000..e27ee578a41 --- /dev/null +++ b/pkg/storage/unified/resource/data/sqlkv_delete_legacy_resource.sql @@ -0,0 +1,5 @@ +DELETE FROM {{ .Ident "resource" }} +WHERE {{ .Ident "group" }} = {{ .Arg .Group }} +AND {{ .Ident "resource" }} = {{ .Arg .Resource }} +AND {{ .Ident "namespace" }} = {{ .Arg .Namespace }} +AND {{ .Ident "name" }} = {{ .Arg .Name }}; diff --git a/pkg/storage/unified/resource/data/sqlkv_insert_datastore.sql b/pkg/storage/unified/resource/data/sqlkv_insert_datastore.sql new file mode 100644 index 00000000000..8372eb73463 --- /dev/null +++ b/pkg/storage/unified/resource/data/sqlkv_insert_datastore.sql @@ -0,0 +1,21 @@ +INSERT INTO {{ .Ident .TableName }} +( + {{ .Ident "guid" }}, + {{ .Ident "key_path" }}, + {{ .Ident "value" }}, + {{ .Ident "group" }}, + {{ .Ident "resource" }}, + {{ .Ident "namespace" }}, + {{ .Ident "name" }}, + {{ .Ident "action" }} +) +VALUES ( + {{ .Arg .GUID }}, + {{ .Arg .KeyPath }}, + COALESCE({{ .Arg .Value }}, ""), + {{ .Arg .Group }}, + {{ .Arg .Resource }}, + {{ .Arg .Namespace }}, + {{ .Arg .Name }}, + {{ .Arg .Action }} +); diff --git a/pkg/storage/unified/resource/data/sqlkv_insert_legacy_resource.sql b/pkg/storage/unified/resource/data/sqlkv_insert_legacy_resource.sql new file mode 100644 index 00000000000..1f58bd28b43 --- /dev/null +++ b/pkg/storage/unified/resource/data/sqlkv_insert_legacy_resource.sql @@ -0,0 +1,31 @@ +INSERT INTO {{ .Ident "resource" }} +( + {{ .Ident "value" }}, + {{ .Ident "guid" }}, + {{ .Ident "group" }}, + {{ .Ident "resource" }}, + {{ .Ident "namespace" }}, + {{ .Ident "name" }}, + {{ .Ident "action" }}, + {{ .Ident "folder" }}, + {{ .Ident "previous_resource_version" }} +) +VALUES ( + COALESCE({{ .Arg .Value }}, ""), + {{ .Arg .GUID }}, + {{ .Arg .Group }}, + {{ .Arg .Resource }}, + {{ .Arg .Namespace }}, + {{ .Arg .Name }}, + {{ .Arg .Action }}, + {{ .Arg .Folder }}, + CASE WHEN {{ .Arg .Action }} = 1 THEN 0 ELSE ( + SELECT {{ .Ident "resource_version" }} + FROM {{ .Ident "resource" }} + WHERE {{ .Ident "group" }} = {{ .Arg .Group }} + AND {{ .Ident "resource" }} = {{ .Arg .Resource }} + AND {{ .Ident "namespace" }} = {{ .Arg .Namespace }} + AND {{ .Ident "name" }} = {{ .Arg .Name }} + ORDER BY {{ .Ident "resource_version" }} DESC LIMIT 1 + ) END +); diff --git a/pkg/storage/unified/resource/data/sqlkv_insert_legacy_resource_history.sql b/pkg/storage/unified/resource/data/sqlkv_insert_legacy_resource_history.sql new file mode 100644 index 00000000000..d52aac5063d --- /dev/null +++ b/pkg/storage/unified/resource/data/sqlkv_insert_legacy_resource_history.sql @@ -0,0 +1,44 @@ +INSERT INTO {{ .Ident "resource_history" }} +( + {{ .Ident "value" }}, + {{ .Ident "guid" }}, + {{ .Ident "group" }}, + {{ .Ident "resource" }}, + {{ .Ident "namespace" }}, + {{ .Ident "name" }}, + {{ .Ident "action" }}, + {{ .Ident "folder" }}, + {{ .Ident "previous_resource_version" }}, + {{ .Ident "generation" }} +) +VALUES ( + COALESCE({{ .Arg .Value }}, ""), + {{ .Arg .GUID }}, + {{ .Arg .Group }}, + {{ .Arg .Resource }}, + {{ .Arg .Namespace }}, + {{ .Arg .Name }}, + {{ .Arg .Action }}, + {{ .Arg .Folder }}, + CASE WHEN {{ .Arg .Action }} = 1 THEN 0 ELSE ( + SELECT {{ .Ident "resource_version" }} + FROM {{ .Ident "resource_history" }} + WHERE {{ .Ident "group" }} = {{ .Arg .Group }} + AND {{ .Ident "resource" }} = {{ .Arg .Resource }} + AND {{ .Ident "namespace" }} = {{ .Arg .Namespace }} + AND {{ .Ident "name" }} = {{ .Arg .Name }} + ORDER BY {{ .Ident "resource_version" }} DESC LIMIT 1 + ) END, + CASE + WHEN {{ .Arg .Action }} = 1 THEN 1 + WHEN {{ .Arg .Action }} = 3 THEN 0 + ELSE 1 + ( + SELECT COUNT(1) + FROM {{ .Ident "resource_history" }} + WHERE {{ .Ident "group" }} = {{ .Arg .Group }} + AND {{ .Ident "resource" }} = {{ .Arg .Resource }} + AND {{ .Ident "namespace" }} = {{ .Arg .Namespace }} + AND {{ .Ident "name" }} = {{ .Arg .Name }} + ) + END +); diff --git a/pkg/storage/unified/resource/data/sqlkv_save_event.sql b/pkg/storage/unified/resource/data/sqlkv_save_event.sql new file mode 100644 index 00000000000..669497dbb19 --- /dev/null +++ b/pkg/storage/unified/resource/data/sqlkv_save_event.sql @@ -0,0 +1,15 @@ +INSERT INTO {{ .Ident .TableName }} +( + {{ .Ident "key_path" }}, + {{ .Ident "value" }} +) +VALUES ( + {{ .Arg .KeyPath }}, + COALESCE({{ .Arg .Value }}, "") +) +{{- if eq .DialectName "mysql" }} +ON DUPLICATE KEY UPDATE {{ .Ident "value" }} = {{ .Arg .Value }} +{{- else }} +ON CONFLICT ({{ .Ident "key_path" }}) DO UPDATE SET {{ .Ident "value" }} = {{ .Arg .Value }} +{{- end }} +; diff --git a/pkg/storage/unified/resource/data/sqlkv_update_datastore.sql b/pkg/storage/unified/resource/data/sqlkv_update_datastore.sql new file mode 100644 index 00000000000..677666e00a4 --- /dev/null +++ b/pkg/storage/unified/resource/data/sqlkv_update_datastore.sql @@ -0,0 +1,3 @@ +UPDATE {{ .Ident .TableName }} +SET {{ .Ident "value" }} = {{ .Arg .Value }} +WHERE {{ .Ident "key_path" }} = {{ .Arg .KeyPath }}; diff --git a/pkg/storage/unified/resource/data/sqlkv_update_legacy_resource.sql b/pkg/storage/unified/resource/data/sqlkv_update_legacy_resource.sql new file mode 100644 index 00000000000..1565d0894a4 --- /dev/null +++ b/pkg/storage/unified/resource/data/sqlkv_update_legacy_resource.sql @@ -0,0 +1,9 @@ +UPDATE {{ .Ident "resource" }} +SET + {{ .Ident "value" }} = {{ .Arg .Value }}, + {{ .Ident "action" }} = {{ .Arg .Action }}, + {{ .Ident "folder" }} = {{ .Arg .Folder }} +WHERE {{ .Ident "group" }} = {{ .Arg .Group }} +AND {{ .Ident "resource" }} = {{ .Arg .Resource }} +AND {{ .Ident "namespace" }} = {{ .Arg .Namespace }} +AND {{ .Ident "name" }} = {{ .Arg .Name }}; diff --git a/pkg/storage/unified/resource/datastore.go b/pkg/storage/unified/resource/datastore.go index 8a930492420..7a1b614323e 100644 --- a/pkg/storage/unified/resource/datastore.go +++ b/pkg/storage/unified/resource/datastore.go @@ -49,6 +49,9 @@ type DataKey struct { ResourceVersion int64 Action DataAction Folder string + + // needed to maintain backwards compatibility with unified/sql + GUID string } // GroupResource represents a unique group/resource combination @@ -61,6 +64,12 @@ func (k DataKey) String() string { return fmt.Sprintf("%s/%s/%s/%s/%d~%s~%s", k.Group, k.Resource, k.Namespace, k.Name, k.ResourceVersion, k.Action, k.Folder) } +// Temporary while we need to support unified/sql/backend compatibility +// Remove once we stop using RvManager in storage_backend.go +func (k DataKey) StringWithGUID() string { + return fmt.Sprintf("%s/%s/%s/%s/%d~%s~%s~%s", k.Group, k.Resource, k.Namespace, k.Name, k.ResourceVersion, k.Action, k.Folder, k.GUID) +} + func (k DataKey) Equals(other DataKey) bool { return k.Group == other.Group && k.Resource == other.Resource && k.Namespace == other.Namespace && k.Name == other.Name && k.ResourceVersion == other.ResourceVersion && k.Action == other.Action && k.Folder == other.Folder } @@ -516,7 +525,13 @@ func (d *dataStore) Save(ctx context.Context, key DataKey, value io.Reader) erro return fmt.Errorf("invalid data key: %w", err) } - writer, err := d.kv.Save(ctx, dataSection, key.String()) + var writer io.WriteCloser + var err error + if key.GUID != "" { + writer, err = d.kv.Save(ctx, dataSection, key.StringWithGUID()) + } else { + writer, err = d.kv.Save(ctx, dataSection, key.String()) + } if err != nil { return err } @@ -583,6 +598,33 @@ func ParseKey(key string) (DataKey, error) { }, nil } +// Temporary while we need to support unified/sql/backend compatibility +// Remove once we stop using RvManager in storage_backend.go +func ParseKeyWithGUID(key string) (DataKey, error) { + parts := strings.Split(key, "/") + if len(parts) != 5 { + return DataKey{}, fmt.Errorf("invalid key: %s", key) + } + rvActionFolderGUIDParts := strings.Split(parts[4], "~") + if len(rvActionFolderGUIDParts) != 4 { + return DataKey{}, fmt.Errorf("invalid key: %s", key) + } + rv, err := strconv.ParseInt(rvActionFolderGUIDParts[0], 10, 64) + if err != nil { + return DataKey{}, fmt.Errorf("invalid resource version '%s' in key %s: %w", rvActionFolderGUIDParts[0], key, err) + } + return DataKey{ + Group: parts[0], + Resource: parts[1], + Namespace: parts[2], + Name: parts[3], + ResourceVersion: rv, + Action: DataAction(rvActionFolderGUIDParts[1]), + Folder: rvActionFolderGUIDParts[2], + GUID: rvActionFolderGUIDParts[3], + }, nil +} + // SameResource checks if this key represents the same resource as another key. // It compares the identifying fields: Group, Resource, Namespace, and Name. // ResourceVersion, Action, and Folder are ignored as they don't identify the resource itself. diff --git a/pkg/storage/unified/resource/eventstore.go b/pkg/storage/unified/resource/eventstore.go index 7f80fb6b87a..e0c01afb550 100644 --- a/pkg/storage/unified/resource/eventstore.go +++ b/pkg/storage/unified/resource/eventstore.go @@ -32,6 +32,7 @@ type EventKey struct { ResourceVersion int64 Action DataAction Folder string + GUID string } func (k EventKey) String() string { diff --git a/pkg/storage/unified/resource/sqlkv.go b/pkg/storage/unified/resource/sqlkv.go index 3c07403296b..9cc2cc32dd0 100644 --- a/pkg/storage/unified/resource/sqlkv.go +++ b/pkg/storage/unified/resource/sqlkv.go @@ -12,8 +12,10 @@ import ( "strings" "text/template" + "github.com/google/uuid" "github.com/grafana/grafana/pkg/storage/unified/sql/db" "github.com/grafana/grafana/pkg/storage/unified/sql/dbutil" + "github.com/grafana/grafana/pkg/storage/unified/sql/rvmanager" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" ) @@ -34,11 +36,18 @@ func mustTemplate(filename string) *template.Template { // Templates. var ( - sqlKVKeys = mustTemplate("sqlkv_keys.sql") - sqlKVGet = mustTemplate("sqlkv_get.sql") - sqlKVBatchGet = mustTemplate("sqlkv_batch_get.sql") - sqlKVDelete = mustTemplate("sqlkv_delete.sql") - sqlKVBatchDelete = mustTemplate("sqlkv_batch_delete.sql") + sqlKVKeys = mustTemplate("sqlkv_keys.sql") + sqlKVGet = mustTemplate("sqlkv_get.sql") + sqlKVBatchGet = mustTemplate("sqlkv_batch_get.sql") + sqlKVSaveEvent = mustTemplate("sqlkv_save_event.sql") + sqlKVInsertData = mustTemplate("sqlkv_insert_datastore.sql") + sqlKVUpdateData = mustTemplate("sqlkv_update_datastore.sql") + sqlKVInsertLegacyResourceHistory = mustTemplate("sqlkv_insert_legacy_resource_history.sql") + sqlKVInsertLegacyResource = mustTemplate("sqlkv_insert_legacy_resource.sql") + sqlKVUpdateLegacyResource = mustTemplate("sqlkv_update_legacy_resource.sql") + sqlKVDeleteLegacyResource = mustTemplate("sqlkv_delete_legacy_resource.sql") + sqlKVDelete = mustTemplate("sqlkv_delete.sql") + sqlKVBatchDelete = mustTemplate("sqlkv_batch_delete.sql") ) // sqlKVSection can be embedded in structs used when rendering query templates @@ -128,6 +137,45 @@ func (req sqlKVBatchRequest) KeyPaths() []string { return result } +type sqlKVSaveRequest struct { + sqltemplate.SQLTemplate + sqlKVSectionKey + Value []byte + + // old fields that can be removed once we prune resource_history + GUID string + Group string + Resource string + Namespace string + Name string + Action int64 + Folder string +} + +func (req sqlKVSaveRequest) Validate() error { + return req.sqlKVSectionKey.Validate() +} + +type sqlKVLegacySaveRequest struct { + sqltemplate.SQLTemplate + Value []byte + GUID string + Group string + Resource string + Namespace string + Name string + Action int64 + Folder string +} + +func (req sqlKVLegacySaveRequest) Validate() error { + return nil +} + +func (req sqlKVLegacySaveRequest) Results() ([]byte, error) { + return req.Value, nil +} + type sqlKVKeysRequest struct { sqltemplate.SQLTemplate sqlKVSection @@ -285,20 +333,187 @@ func (k *sqlKV) BatchGet(ctx context.Context, section string, keys []string) ite } } -// TODO: this function only exists to support the testing of the sqlkv implementation before -// we have a proper implementation of `Save`. -func (k *sqlKV) TestingSave(ctx context.Context, key string, value []byte) error { - stmt := fmt.Sprintf( - `INSERT INTO resource_events (key_path, value) VALUES (%s, %s)`, - k.dialect.ArgPlaceholder(1), k.dialect.ArgPlaceholder(2), - ) +func (k *sqlKV) Save(ctx context.Context, section string, key string) (io.WriteCloser, error) { + sectionKey := sqlKVSectionKey{sqlKVSection{section}, key} + if err := sectionKey.Validate(); err != nil { + return nil, err + } - _, err := k.db.ExecContext(ctx, stmt, eventsSection+"/"+key, value) - return err + return &sqlWriteCloser{ + kv: k, + ctx: ctx, + sectionKey: sectionKey, + buf: &bytes.Buffer{}, + closed: false, + }, nil } -func (k *sqlKV) Save(ctx context.Context, section string, key string) (io.WriteCloser, error) { - panic("not implemented!") +type sqlWriteCloser struct { + kv *sqlKV + ctx context.Context + sectionKey sqlKVSectionKey + buf *bytes.Buffer + closed bool +} + +func (w *sqlWriteCloser) Write(value []byte) (int, error) { + if w.closed { + return 0, errors.New("write to closed writer") + } + + return w.buf.Write(value) +} + +func (w *sqlWriteCloser) Close() error { + if w.closed { + return nil + } + + w.closed = true + + // do regular kv save: simple key_path + value insert with conflict check. + // can only do this on resource_events for now, until we drop the columns in resource_history + if w.sectionKey.Section == eventsSection { + _, err := dbutil.Exec(w.ctx, w.kv.db, sqlKVSaveEvent, sqlKVSaveRequest{ + SQLTemplate: sqltemplate.New(w.kv.dialect), + sqlKVSectionKey: w.sectionKey, + Value: w.buf.Bytes(), + }) + + if err != nil { + return fmt.Errorf("failed to save: %w", err) + } + + return nil + } + + // if storage_backend is running with an RvManager, it will inject a transaction into the context + // used to keep backwards compatibility between sql-based kvstore and unified/sql/backend + tx, ok := rvmanager.TxFromCtx(w.ctx) + if !ok { + // temporary save for dataStore without rvmanager + // we can use the same template as the event one after we: + // - move PK from GUID to key_path + // - remove all unnecessary columns (or at least their NOT NULL constraints) + _, err := w.kv.Get(w.ctx, w.sectionKey.Section, w.sectionKey.Key) + if errors.Is(err, ErrNotFound) { + _, err := dbutil.Exec(w.ctx, w.kv.db, sqlKVInsertData, sqlKVSaveRequest{ + SQLTemplate: sqltemplate.New(w.kv.dialect), + sqlKVSectionKey: w.sectionKey, + GUID: uuid.New().String(), + Value: w.buf.Bytes(), + }) + + if err != nil { + return fmt.Errorf("failed to insert to datastore: %w", err) + } + + return nil + } + + if err != nil { + return fmt.Errorf("failed to get for save: %w", err) + } + + _, err = dbutil.Exec(w.ctx, w.kv.db, sqlKVUpdateData, sqlKVSaveRequest{ + SQLTemplate: sqltemplate.New(w.kv.dialect), + sqlKVSectionKey: w.sectionKey, + Value: w.buf.Bytes(), + }) + + if err != nil { + return fmt.Errorf("failed to update to datastore: %w", err) + } + + return nil + } + + // special, temporary save that includes all the fields in resource_history that are not relevant for the kvstore, + // as well as the resource table. This is only called if an RvManager was passed to storage_backend, as that + // component will be responsible for populating the resource_version and key_path columns + // note that we are not touching resource_version table, neither the resource_version columns or the key_path column + // as the RvManager will be responsible for this + dataKey, err := ParseKeyWithGUID(w.sectionKey.Key) + if err != nil { + return fmt.Errorf("failed to parse key: %w", err) + } + + var action int64 + switch dataKey.Action { + case DataActionCreated: + action = 1 + case DataActionUpdated: + action = 2 + case DataActionDeleted: + action = 3 + default: + return fmt.Errorf("failed to parse key: %w", err) + } + + _, err = dbutil.Exec(w.ctx, tx, sqlKVInsertLegacyResourceHistory, sqlKVSaveRequest{ + SQLTemplate: sqltemplate.New(w.kv.dialect), + sqlKVSectionKey: w.sectionKey, + Value: w.buf.Bytes(), + GUID: dataKey.GUID, + Group: dataKey.Group, + Resource: dataKey.Resource, + Namespace: dataKey.Namespace, + Name: dataKey.Name, + Action: action, + Folder: dataKey.Folder, + }) + + if err != nil { + return fmt.Errorf("failed to save to resource_history: %w", err) + } + + switch dataKey.Action { + case DataActionCreated: + _, err = dbutil.Exec(w.ctx, tx, sqlKVInsertLegacyResource, sqlKVLegacySaveRequest{ + SQLTemplate: sqltemplate.New(w.kv.dialect), + Value: w.buf.Bytes(), + GUID: dataKey.GUID, + Group: dataKey.Group, + Resource: dataKey.Resource, + Namespace: dataKey.Namespace, + Name: dataKey.Name, + Action: action, + Folder: dataKey.Folder, + }) + + if err != nil { + return fmt.Errorf("failed to insert to resource: %w", err) + } + case DataActionUpdated: + _, err = dbutil.Exec(w.ctx, tx, sqlKVUpdateLegacyResource, sqlKVLegacySaveRequest{ + SQLTemplate: sqltemplate.New(w.kv.dialect), + Value: w.buf.Bytes(), + Group: dataKey.Group, + Resource: dataKey.Resource, + Namespace: dataKey.Namespace, + Name: dataKey.Name, + Action: action, + Folder: dataKey.Folder, + }) + + if err != nil { + return fmt.Errorf("failed to update resource: %w", err) + } + case DataActionDeleted: + _, err = dbutil.Exec(w.ctx, tx, sqlKVDeleteLegacyResource, sqlKVLegacySaveRequest{ + SQLTemplate: sqltemplate.New(w.kv.dialect), + Group: dataKey.Group, + Resource: dataKey.Resource, + Namespace: dataKey.Namespace, + Name: dataKey.Name, + }) + + if err != nil { + return fmt.Errorf("failed to delete from resource: %w", err) + } + } + + return nil } func (k *sqlKV) Delete(ctx context.Context, section string, key string) error { @@ -319,6 +534,8 @@ func (k *sqlKV) Delete(ctx context.Context, section string, key string) error { return ErrNotFound } + // TODO reflect change to resource table + return nil } diff --git a/pkg/storage/unified/resource/storage_backend.go b/pkg/storage/unified/resource/storage_backend.go index b0f51702775..13f2b9d6159 100644 --- a/pkg/storage/unified/resource/storage_backend.go +++ b/pkg/storage/unified/resource/storage_backend.go @@ -14,6 +14,7 @@ import ( "time" "github.com/bwmarrin/snowflake" + "github.com/google/uuid" "github.com/grafana/grafana-app-sdk/logging" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/trace" @@ -22,6 +23,8 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" + "github.com/grafana/grafana/pkg/storage/unified/sql/db" + "github.com/grafana/grafana/pkg/storage/unified/sql/rvmanager" "github.com/grafana/grafana/pkg/util/debouncer" ) @@ -68,6 +71,8 @@ type kvStorageBackend struct { withExperimentalClusterScope bool //tracer trace.Tracer //reg prometheus.Registerer + + rvManager *rvmanager.ResourceVersionManager } var _ KVBackend = &kvStorageBackend{} @@ -85,6 +90,10 @@ type KVBackendOptions struct { EventPruningInterval time.Duration // How often to run the event pruning (default: 5 minutes) Tracer trace.Tracer // TODO add tracing Reg prometheus.Registerer // TODO add metrics + + // Adding RvManager overrides the RV generated with snowflake in order to keep backwards compatibility with + // unified/sql + RvManager *rvmanager.ResourceVersionManager } func NewKVStorageBackend(opts KVBackendOptions) (KVBackend, error) { @@ -119,6 +128,7 @@ func NewKVStorageBackend(opts KVBackendOptions) (KVBackend, error) { eventRetentionPeriod: eventRetentionPeriod, eventPruningInterval: eventPruningInterval, withExperimentalClusterScope: opts.WithExperimentalClusterScope, + rvManager: opts.RvManager, } err = backend.initPruner(ctx) if err != nil { @@ -317,9 +327,28 @@ func (k *kvStorageBackend) WriteEvent(ctx context.Context, event WriteEvent) (in Action: action, Folder: obj.GetFolder(), } - err := k.dataStore.Save(ctx, dataKey, bytes.NewReader(event.Value)) - if err != nil { - return 0, fmt.Errorf("failed to write data: %w", err) + + if k.rvManager != nil { + dataKey.GUID = uuid.New().String() + var err error + rv, err = k.rvManager.ExecWithRV(ctx, event.Key, func(tx db.Tx) (string, error) { + err := k.dataStore.Save(rvmanager.ContextWithTx(ctx, tx), dataKey, bytes.NewReader(event.Value)) + if err != nil { + return "", fmt.Errorf("failed to write data: %w", err) + } + + return dataKey.GUID, nil + }) + if err != nil { + return 0, fmt.Errorf("failed to write data: %w", err) + } + + dataKey.ResourceVersion = rv + } else { + err := k.dataStore.Save(ctx, dataKey, bytes.NewReader(event.Value)) + if err != nil { + return 0, fmt.Errorf("failed to write data: %w", err) + } } // Optimistic concurrency control to verify our write is the latest version @@ -340,14 +369,22 @@ func (k *kvStorageBackend) WriteEvent(ctx context.Context, event WriteEvent) (in } // Check if the RV we just wrote is the latest. If not, a concurrent write with higher RV happened - if latestKey.ResourceVersion != rv { + if !rvmanager.IsRvEqual(latestKey.ResourceVersion, rv) { // Delete the data we just wrote since it's not the latest + // if we're running with rvManager, convert the ResourceVersion back to snowflake to delete + if k.rvManager != nil { + dataKey.ResourceVersion = rvmanager.SnowflakeFromRv(dataKey.ResourceVersion) + } _ = k.dataStore.Delete(ctx, dataKey) return 0, fmt.Errorf("optimistic locking failed: concurrent modification detected") } - if prevKey.ResourceVersion != event.PreviousRV { + if !rvmanager.IsRvEqual(prevKey.ResourceVersion, event.PreviousRV) { // Another concurrent write happened between our read and write + // if we're running with rvManager, convert the ResourceVersion back to snowflake to delete + if k.rvManager != nil { + dataKey.ResourceVersion = rvmanager.SnowflakeFromRv(dataKey.ResourceVersion) + } _ = k.dataStore.Delete(ctx, dataKey) return 0, fmt.Errorf("optimistic locking failed: resource was modified concurrently (expected previous RV %d, found %d)", event.PreviousRV, prevKey.ResourceVersion) } @@ -366,8 +403,12 @@ func (k *kvStorageBackend) WriteEvent(ctx context.Context, event WriteEvent) (in } // Check if the RV we just wrote is the latest. If not, a concurrent create with higher RV happened - if latestKey.ResourceVersion != rv { + if !rvmanager.IsRvEqual(latestKey.ResourceVersion, rv) { // Delete the data we just wrote since it's not the latest + // if we're running with rvManager, convert the ResourceVersion back to snowflake to delete + if k.rvManager != nil { + dataKey.ResourceVersion = rvmanager.SnowflakeFromRv(dataKey.ResourceVersion) + } _ = k.dataStore.Delete(ctx, dataKey) return 0, fmt.Errorf("optimistic locking failed: concurrent create detected") } @@ -375,6 +416,10 @@ func (k *kvStorageBackend) WriteEvent(ctx context.Context, event WriteEvent) (in // Verify that the immediate predecessor is not a create if prevKey.Action == DataActionCreated { // Another concurrent create happened - delete our write and return error + // if we're running with rvManager, convert the ResourceVersion back to snowflake to delete + if k.rvManager != nil { + dataKey.ResourceVersion = rvmanager.SnowflakeFromRv(dataKey.ResourceVersion) + } _ = k.dataStore.Delete(ctx, dataKey) return 0, fmt.Errorf("optimistic locking failed: concurrent create detected") } @@ -391,7 +436,7 @@ func (k *kvStorageBackend) WriteEvent(ctx context.Context, event WriteEvent) (in Folder: obj.GetFolder(), PreviousRV: event.PreviousRV, } - err = k.eventStore.Save(ctx, eventData) + err := k.eventStore.Save(ctx, eventData) if err != nil { // Clean up the data we wrote since event save failed _ = k.dataStore.Delete(ctx, dataKey) diff --git a/pkg/storage/unified/sql/rvmanager/rv_manager.go b/pkg/storage/unified/sql/rvmanager/rv_manager.go index b4f3b5de596..b10685f22ad 100644 --- a/pkg/storage/unified/sql/rvmanager/rv_manager.go +++ b/pkg/storage/unified/sql/rvmanager/rv_manager.go @@ -21,6 +21,19 @@ import ( "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" ) +type contextKey string + +const txKey contextKey = "rvmanager_db_tx" + +func ContextWithTx(ctx context.Context, tx db.Tx) context.Context { + return context.WithValue(ctx, txKey, tx) +} + +func TxFromCtx(ctx context.Context) (db.Tx, bool) { + tx, ok := ctx.Value(txKey).(db.Tx) + return tx, ok +} + var tracer = otel.Tracer("github.com/grafana/grafana/pkg/storage/unified/sql/rvmanager") var ( @@ -294,7 +307,7 @@ func (m *ResourceVersionManager) execBatch(ctx context.Context, group, resource // Allocate the RVs for i, guid := range guids { guidToRV[guid] = rv - guidToSnowflakeRV[guid] = snowflakeFromRv(rv) + guidToSnowflakeRV[guid] = SnowflakeFromRv(rv) rvs[i] = rv rv++ } @@ -353,10 +366,20 @@ func (m *ResourceVersionManager) execBatch(ctx context.Context, group, resource // takes a unix microsecond rv and transforms into a snowflake format. The timestamp is converted from microsecond to // millisecond (the integer division) and the remainder is saved in the stepbits section. machine id is always 0 -func snowflakeFromRv(rv int64) int64 { +func SnowflakeFromRv(rv int64) int64 { return (((rv / 1000) - snowflake.Epoch) << (snowflake.NodeBits + snowflake.StepBits)) + (rv % 1000) } +// helper utility to compare two RVs. The first RV must be in snowflake format. Will convert rv2 to snowflake and retry +// if comparison fails +func IsRvEqual(rv1, rv2 int64) bool { + if rv1 == rv2 { + return true + } + + return rv1 == SnowflakeFromRv(rv2) +} + // Lock locks the resource version for the given key func (m *ResourceVersionManager) Lock(ctx context.Context, x db.ContextExecer, group, resource string) (nextRV int64, err error) { // 1. Lock the row and prevent concurrent updates until the transaction is committed diff --git a/pkg/storage/unified/sql/server.go b/pkg/storage/unified/sql/server.go index 84eda71ca20..f4a1ee3ce77 100644 --- a/pkg/storage/unified/sql/server.go +++ b/pkg/storage/unified/sql/server.go @@ -20,6 +20,8 @@ import ( "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/sql/db/dbimpl" + "github.com/grafana/grafana/pkg/storage/unified/sql/rvmanager" + "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" ) type QOSEnqueueDequeuer interface { @@ -103,11 +105,34 @@ func NewResourceServer(opts ServerOptions) (resource.ResourceServer, error) { return nil, fmt.Errorf("error creating sqlkv: %s", err) } - kvBackend, err := resource.NewKVStorageBackend(resource.KVBackendOptions{ + kvBackendOpts := resource.KVBackendOptions{ KvStore: sqlkv, Tracer: opts.Tracer, Reg: opts.Reg, + } + + ctx := context.Background() + dbConn, err := eDB.Init(ctx) + if err != nil { + return nil, fmt.Errorf("error initializing DB: %w", err) + } + + dialect := sqltemplate.DialectForDriver(dbConn.DriverName()) + if dialect == nil { + return nil, fmt.Errorf("unsupported database driver: %s", dbConn.DriverName()) + } + + rvManager, err := rvmanager.NewResourceVersionManager(rvmanager.ResourceManagerOptions{ + Dialect: dialect, + DB: dbConn, }) + if err != nil { + return nil, fmt.Errorf("failed to create resource version manager: %w", err) + } + + // TODO add config to decide whether to pass RvManager or not + kvBackendOpts.RvManager = rvManager + kvBackend, err := resource.NewKVStorageBackend(kvBackendOpts) if err != nil { return nil, fmt.Errorf("error creating kv backend: %s", err) } diff --git a/pkg/storage/unified/testing/kv.go b/pkg/storage/unified/testing/kv.go index 770bf9ac6e6..d1900c7a46e 100644 --- a/pkg/storage/unified/testing/kv.go +++ b/pkg/storage/unified/testing/kv.go @@ -148,14 +148,13 @@ func runTestKVGet(t *testing.T, kv resource.KV, nsPrefix string) { func runTestKVSave(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) - section := nsPrefix + "-save" t.Run("save new key", func(t *testing.T) { testValue := "new test value" - saveKVHelper(t, kv, ctx, section, "new-key", strings.NewReader(testValue)) + saveKVHelper(t, kv, ctx, testSection, "new-key", strings.NewReader(testValue)) // Verify it was saved - reader, err := kv.Get(ctx, section, "new-key") + reader, err := kv.Get(ctx, testSection, "new-key") require.NoError(t, err) value, err := io.ReadAll(reader) @@ -166,6 +165,26 @@ func runTestKVSave(t *testing.T, kv resource.KV, nsPrefix string) { }) t.Run("save overwrite existing key", func(t *testing.T) { + // First save + saveKVHelper(t, kv, ctx, testSection, "overwrite-key", strings.NewReader("old value")) + + // Overwrite + newValue := "new value" + saveKVHelper(t, kv, ctx, testSection, "overwrite-key", strings.NewReader(newValue)) + + // Verify it was updated + reader, err := kv.Get(ctx, testSection, "overwrite-key") + require.NoError(t, err) + + value, err := io.ReadAll(reader) + require.NoError(t, err) + assert.Equal(t, newValue, string(value)) + err = reader.Close() + require.NoError(t, err) + }) + + t.Run("save overwrite existing key (datastore)", func(t *testing.T) { + section := "unified/data" // First save saveKVHelper(t, kv, ctx, section, "overwrite-key", strings.NewReader("old value")) @@ -192,10 +211,10 @@ func runTestKVSave(t *testing.T, kv resource.KV, nsPrefix string) { t.Run("save binary data", func(t *testing.T) { binaryData := []byte{0x00, 0x01, 0x02, 0x03, 0xFF, 0xFE, 0xFD} - saveKVHelper(t, kv, ctx, section, "binary-key", bytes.NewReader(binaryData)) + saveKVHelper(t, kv, ctx, testSection, "binary-key", bytes.NewReader(binaryData)) // Verify binary data - reader, err := kv.Get(ctx, section, "binary-key") + reader, err := kv.Get(ctx, testSection, "binary-key") require.NoError(t, err) value, err := io.ReadAll(reader) @@ -207,10 +226,10 @@ func runTestKVSave(t *testing.T, kv resource.KV, nsPrefix string) { t.Run("save key with no data", func(t *testing.T) { // Save a key with empty data - saveKVHelper(t, kv, ctx, section, "empty-key", strings.NewReader("")) + saveKVHelper(t, kv, ctx, testSection, "empty-key", strings.NewReader("")) // Verify it was saved with empty data - reader, err := kv.Get(ctx, section, "empty-key") + reader, err := kv.Get(ctx, testSection, "empty-key") require.NoError(t, err) value, err := io.ReadAll(reader) @@ -907,18 +926,6 @@ func runTestKVBatchDelete(t *testing.T, kv resource.KV, nsPrefix string) { func saveKVHelper(t *testing.T, kv resource.KV, ctx context.Context, section, key string, value io.Reader) { t.Helper() - // TODO: remove this check once the sqlkv implementation supports `Save`. - type testingSaver interface { - TestingSave(context.Context, string, []byte) error - } - - if saver, ok := kv.(testingSaver); ok { - blob, err := io.ReadAll(value) - require.NoError(t, err) - require.NoError(t, saver.TestingSave(ctx, key, blob)) - return - } - writer, err := kv.Save(ctx, section, key) require.NoError(t, err) _, err = io.Copy(writer, value) diff --git a/pkg/storage/unified/testing/kv_test.go b/pkg/storage/unified/testing/kv_test.go index 3814df17b7e..5e94ccd8a7f 100644 --- a/pkg/storage/unified/testing/kv_test.go +++ b/pkg/storage/unified/testing/kv_test.go @@ -47,7 +47,6 @@ func TestSQLKV(t *testing.T) { }, &KVTestOptions{ NSPrefix: "sql-kv-test", SkipTests: map[string]bool{ - TestKVSave: true, TestKVConcurrent: true, TestKVUnixTimestamp: true, }, From 2fbe2f77e389d59ea84a63fb095d3bc887e72009 Mon Sep 17 00:00:00 2001 From: Stephanie Hingtgen Date: Fri, 19 Dec 2025 13:17:39 -0700 Subject: [PATCH 270/297] Folders: Add max depth check with descendant to /apis (#115305) --- pkg/api/apierrors/folder.go | 3 +- pkg/api/apierrors/folder_test.go | 2 +- pkg/api/folder.go | 2 +- pkg/registry/apis/folders/register.go | 2 +- pkg/registry/apis/folders/register_test.go | 4 + pkg/registry/apis/folders/validate.go | 162 +++++++++++++++++- pkg/registry/apis/folders/validate_test.go | 113 +++++++++++- .../folderimpl/folder_unifiedstorage.go | 38 +--- 8 files changed, 275 insertions(+), 51 deletions(-) diff --git a/pkg/api/apierrors/folder.go b/pkg/api/apierrors/folder.go index 9509ff4ff55..a938b165852 100644 --- a/pkg/api/apierrors/folder.go +++ b/pkg/api/apierrors/folder.go @@ -29,7 +29,8 @@ func ToFolderErrorResponse(err error) response.Response { errors.Is(err, dashboards.ErrDashboardTypeMismatch) || errors.Is(err, dashboards.ErrDashboardInvalidUid) || errors.Is(err, dashboards.ErrDashboardUidTooLong) || - errors.Is(err, folder.ErrFolderCannotBeParentOfItself) { + errors.Is(err, folder.ErrFolderCannotBeParentOfItself) || + errors.Is(err, folder.ErrMaximumDepthReached) { return response.Error(http.StatusBadRequest, err.Error(), nil) } diff --git a/pkg/api/apierrors/folder_test.go b/pkg/api/apierrors/folder_test.go index 0ca8b16fc87..233254dc6cc 100644 --- a/pkg/api/apierrors/folder_test.go +++ b/pkg/api/apierrors/folder_test.go @@ -30,7 +30,7 @@ func TestToFolderErrorResponse(t *testing.T) { { name: "maximum depth reached", input: folder.ErrMaximumDepthReached.Errorf("Maximum nested folder depth reached"), - want: response.Err(folder.ErrMaximumDepthReached.Errorf("Maximum nested folder depth reached")), + want: response.Error(http.StatusBadRequest, "[folder.maximum-depth-reached] Maximum nested folder depth reached", nil), }, { name: "bad request errors", diff --git a/pkg/api/folder.go b/pkg/api/folder.go index 851910a9e1d..5b0bf9d121e 100644 --- a/pkg/api/folder.go +++ b/pkg/api/folder.go @@ -214,7 +214,7 @@ func (hs *HTTPServer) MoveFolder(c *contextmodel.ReqContext) response.Response { cmd.SignedInUser = c.SignedInUser theFolder, err := hs.folderService.Move(c.Req.Context(), &cmd) if err != nil { - return response.ErrOrFallback(http.StatusInternalServerError, "move folder failed", err) + return apierrors.ToFolderErrorResponse(err) } folderDTO, err := hs.newToFolderDto(c, theFolder) diff --git a/pkg/registry/apis/folders/register.go b/pkg/registry/apis/folders/register.go index 64e51c06312..7c39fbe22ae 100644 --- a/pkg/registry/apis/folders/register.go +++ b/pkg/registry/apis/folders/register.go @@ -356,7 +356,7 @@ func (b *FolderAPIBuilder) Validate(ctx context.Context, a admission.Attributes, if !ok { return fmt.Errorf("obj is not folders.Folder") } - return validateOnUpdate(ctx, f, old, b.storage, b.parents, folder.MaxNestedFolderDepth) + return validateOnUpdate(ctx, f, old, b.storage, b.parents, b.searcher, folder.MaxNestedFolderDepth) default: return nil } diff --git a/pkg/registry/apis/folders/register_test.go b/pkg/registry/apis/folders/register_test.go index 066f8793776..1eb00f0a7e5 100644 --- a/pkg/registry/apis/folders/register_test.go +++ b/pkg/registry/apis/folders/register_test.go @@ -376,6 +376,10 @@ func TestFolderAPIBuilder_Validate_Update(t *testing.T) { m.On("Get", mock.Anything, "new-parent", mock.Anything).Return( &folders.Folder{}, nil).Once() + // also retrieves old parent for depth difference calculation + m.On("Get", mock.Anything, "valid-parent", mock.Anything).Return( + &folders.Folder{}, + nil).Once() }, }, { diff --git a/pkg/registry/apis/folders/validate.go b/pkg/registry/apis/folders/validate.go index eb0c29b30a1..3c7c30a1aea 100644 --- a/pkg/registry/apis/folders/validate.go +++ b/pkg/registry/apis/folders/validate.go @@ -6,6 +6,7 @@ import ( "slices" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/selection" "k8s.io/apiserver/pkg/registry/rest" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" @@ -13,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/folder" + "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/resourcepb" "github.com/grafana/grafana/pkg/util" ) @@ -73,6 +75,7 @@ func validateOnUpdate(ctx context.Context, old *folders.Folder, getter rest.Getter, parents parentsGetter, + searcher resourcepb.ResourceIndexClient, maxDepth int, ) error { folderObj, err := utils.MetaAccessor(obj) @@ -95,7 +98,10 @@ func validateOnUpdate(ctx context.Context, // Validate the move operation newParent := folderObj.GetFolder() - // If we move to root, we don't need to validate the depth. + // If we move to root, we don't need to validate the depth, because the folder already existed + // before and wasn't too deep. This move will make it more shallow. + // + // We also don't need to validate circular references because the root folder cannot have a parent. if newParent == folder.RootFolderUID { return nil } @@ -113,9 +119,6 @@ func validateOnUpdate(ctx context.Context, if !ok { return fmt.Errorf("expected folder, found %T", parentObj) } - - //FIXME: until we have a way to represent the tree, we can only - // look at folder parents to check how deep the new folder tree will be info, err := parents(ctx, parent) if err != nil { return err @@ -129,13 +132,162 @@ func validateOnUpdate(ctx context.Context, } } - // if by moving a folder we exceed the max depth, return an error + // if by moving a folder we exceed the max depth just from its parents + itself, return an error if len(info.Items) > maxDepth+1 { return folder.ErrMaximumDepthReached.Errorf("maximum folder depth reached") } + // To try to save some computation, get the parents of the old parent (this is typically cheaper + // than looking at the children of the folder). If the old parent has more parents or the same + // number of parents as the new parent, we can return early, because we know the folder had to be + // safe from the creation validation. If we cannot access the older parent, we will continue to check the children. + if canSkipChildrenCheck(ctx, oldFolder, getter, parents, len(info.Items)) { + return nil + } + + // Now comes the more expensive part: we need to check if moving this folder will cause + // any descendant folders to exceed the max depth. + // + // Calculate the maximum allowed subtree depth after the move. + allowedDepth := (maxDepth + 1) - len(info.Items) + if allowedDepth <= 0 { + return nil + } + + return checkSubtreeDepth(ctx, searcher, obj.Namespace, obj.Name, allowedDepth, maxDepth) +} + +// canSkipChildrenCheck determines if we can skip the expensive children depth check. +// If the old parent depth is >= the new parent depth, the folder was already valid +// and this move won't make descendants exceed max depth. +func canSkipChildrenCheck(ctx context.Context, oldFolder utils.GrafanaMetaAccessor, getter rest.Getter, parents parentsGetter, newParentDepth int) bool { + if oldFolder.GetFolder() == folder.RootFolderUID { + return false + } + + oldParentObj, err := getter.Get(ctx, oldFolder.GetFolder(), &metav1.GetOptions{}) + if err != nil { + return false + } + + oldParent, ok := oldParentObj.(*folders.Folder) + if !ok { + return false + } + + oldInfo, err := parents(ctx, oldParent) + if err != nil { + return false + } + + oldParentDepth := len(oldInfo.Items) + levelDifference := newParentDepth - oldParentDepth + return levelDifference <= 0 +} + +// checkSubtreeDepth uses a hybrid DFS+batching approach: +// 1. fetches one page of children for the current folder(s) +// 2. batches all those children into one request to get their children +// 3. continues depth-first (batching still) until max depth or violation +// 4. only fetches more siblings after fully exploring current batch +func checkSubtreeDepth(ctx context.Context, searcher resourcepb.ResourceIndexClient, namespace string, folderUID string, remainingDepth int, maxDepth int) error { + if remainingDepth <= 0 { + return nil + } + + // Start with the folder being moved + return checkSubtreeDepthBatched(ctx, searcher, namespace, []string{folderUID}, remainingDepth, maxDepth) +} + +// checkSubtreeDepthBatched checks depth for a batch of folders at the same level +func checkSubtreeDepthBatched(ctx context.Context, searcher resourcepb.ResourceIndexClient, namespace string, parentUIDs []string, remainingDepth int, maxDepth int) error { + if remainingDepth <= 0 || len(parentUIDs) == 0 { + return nil + } + + const pageSize int64 = 1000 + var offset int64 + totalPages := 0 + hasMore := true + + // Using an upper limit to ensure no infinite loops can happen + for hasMore && totalPages < 1000 { + totalPages++ + + var err error + var children []string + children, hasMore, err = getChildrenBatch(ctx, searcher, namespace, parentUIDs, pageSize, offset) + if err != nil { + return fmt.Errorf("failed to get children: %w", err) + } + + if len(children) == 0 { + return nil + } + + // if we are at the last allowed depth and children exist, we will hit the max + if remainingDepth == 1 { + return folder.ErrMaximumDepthReached.Errorf("maximum folder depth %d would be exceeded after move", maxDepth) + } + + if err := checkSubtreeDepthBatched(ctx, searcher, namespace, children, remainingDepth-1, maxDepth); err != nil { + return err + } + + if !hasMore { + return nil + } + + offset += pageSize + } + return nil } +// getChildrenBatch fetches children for multiple parents +func getChildrenBatch(ctx context.Context, searcher resourcepb.ResourceIndexClient, namespace string, parentUIDs []string, limit int64, offset int64) ([]string, bool, error) { + if len(parentUIDs) == 0 { + return nil, false, nil + } + + resp, err := searcher.Search(ctx, &resourcepb.ResourceSearchRequest{ + Options: &resourcepb.ListOptions{ + Key: &resourcepb.ResourceKey{ + Namespace: namespace, + Group: folders.FolderResourceInfo.GroupVersionResource().Group, + Resource: folders.FolderResourceInfo.GroupVersionResource().Resource, + }, + Fields: []*resourcepb.Requirement{{ + Key: resource.SEARCH_FIELD_FOLDER, + Operator: string(selection.In), + Values: parentUIDs, + }}, + }, + Limit: limit, + Offset: offset, + }) + if err != nil { + return nil, false, fmt.Errorf("failed to search folders: %w", err) + } + + if resp.Error != nil { + return nil, false, fmt.Errorf("search error: %s", resp.Error.Message) + } + + if resp.Results == nil || len(resp.Results.Rows) == 0 { + return nil, false, nil + } + + children := make([]string, 0, len(resp.Results.Rows)) + for _, row := range resp.Results.Rows { + if row.Key != nil { + children = append(children, row.Key.Name) + } + } + + hasMore := resp.Results.NextPageToken != "" + return children, hasMore, nil +} + func validateOnDelete(ctx context.Context, f *folders.Folder, searcher resourcepb.ResourceIndexClient, diff --git a/pkg/registry/apis/folders/validate_test.go b/pkg/registry/apis/folders/validate_test.go index 7fdb3cfae12..b9f55571ce0 100644 --- a/pkg/registry/apis/folders/validate_test.go +++ b/pkg/registry/apis/folders/validate_test.go @@ -282,6 +282,7 @@ func TestValidateUpdate(t *testing.T) { old *folders.Folder parents *folders.FolderInfoList parentsError error + allFolders []folders.Folder expectedErr string maxDepth int // defaults to 5 unless set }{ @@ -454,6 +455,74 @@ func TestValidateUpdate(t *testing.T) { }, expectedErr: "cannot move folder under its own descendant", }, + { + name: "error when moving folder from root to level2 with children exceeds max depth", + folder: &folders.Folder{ + ObjectMeta: metav1.ObjectMeta{ + Name: "folderWithChildren", + Annotations: map[string]string{ + utils.AnnoKeyFolder: "level2", + }, + }, + Spec: folders.FolderSpec{ + Title: "folder with children", + }, + }, + old: &folders.Folder{ + ObjectMeta: metav1.ObjectMeta{ + Name: "folderWithChildren", + }, + Spec: folders.FolderSpec{ + Title: "folder with children", + }, + }, + parents: &folders.FolderInfoList{ + Items: []folders.FolderInfo{ + {Name: "level2", Parent: "level1"}, + {Name: "level1", Parent: folder.GeneralFolderUID}, + {Name: folder.GeneralFolderUID}, + }, + }, + allFolders: []folders.Folder{ + {ObjectMeta: metav1.ObjectMeta{Name: "child1", Annotations: map[string]string{utils.AnnoKeyFolder: "folderWithChildren"}}}, + {ObjectMeta: metav1.ObjectMeta{Name: "grandchild1", Annotations: map[string]string{utils.AnnoKeyFolder: "child1"}}}, + }, + maxDepth: 4, + expectedErr: "[folder.maximum-depth-reached]", + }, + { + name: "can move folder from root level to level1 with children when within max depth", + folder: &folders.Folder{ + ObjectMeta: metav1.ObjectMeta{ + Name: "folderWithChildren", + Annotations: map[string]string{ + utils.AnnoKeyFolder: "level1", + }, + }, + Spec: folders.FolderSpec{ + Title: "folder with children", + }, + }, + old: &folders.Folder{ + ObjectMeta: metav1.ObjectMeta{ + Name: "folderWithChildren", + }, + Spec: folders.FolderSpec{ + Title: "folder with children", + }, + }, + parents: &folders.FolderInfoList{ + Items: []folders.FolderInfo{ + {Name: "level1", Parent: folder.GeneralFolderUID}, + {Name: folder.GeneralFolderUID}, + }, + }, + allFolders: []folders.Folder{ + {ObjectMeta: metav1.ObjectMeta{Name: "child1", Annotations: map[string]string{utils.AnnoKeyFolder: "folderWithChildren"}}}, + {ObjectMeta: metav1.ObjectMeta{Name: "grandchild1", Annotations: map[string]string{utils.AnnoKeyFolder: "child1"}}}, + }, + maxDepth: 4, + }, } for _, tt := range tests { @@ -474,11 +543,17 @@ func TestValidateUpdate(t *testing.T) { }, nil).Maybe() } } + for i := range tt.allFolders { + f := tt.allFolders[i] + m.On("Get", context.Background(), f.Name, &metav1.GetOptions{}).Return(&f, nil).Maybe() + } err := validateOnUpdate(context.Background(), tt.folder, tt.old, m, func(ctx context.Context, folder *folders.Folder) (*folders.FolderInfoList, error) { return tt.parents, tt.parentsError - }, maxDepth) + }, + &mockSearchClient{folders: tt.allFolders}, + maxDepth) if tt.expectedErr == "" { require.NoError(t, err) @@ -693,8 +768,7 @@ type mockSearchClient struct { stats *resourcepb.ResourceStatsResponse statsErr error - search *resourcepb.ResourceSearchResponse - searchErr error + folders []folders.Folder } // GetStats implements resourcepb.ResourceIndexClient. @@ -703,8 +777,37 @@ func (m *mockSearchClient) GetStats(ctx context.Context, in *resourcepb.Resource } // Search implements resourcepb.ResourceIndexClient. -func (m *mockSearchClient) Search(ctx context.Context, in *resourcepb.ResourceSearchRequest, opts ...grpc.CallOption) (*resourcepb.ResourceSearchResponse, error) { - return m.search, m.searchErr +func (m *mockSearchClient) Search(ctx context.Context, req *resourcepb.ResourceSearchRequest, opts ...grpc.CallOption) (*resourcepb.ResourceSearchResponse, error) { + // get the list of parents from the search request + parentSet := make(map[string]bool) + if req.Options != nil && req.Options.Fields != nil { + for _, field := range req.Options.Fields { + if field.Key == "folder" && field.Operator == "in" { + for _, v := range field.Values { + parentSet[v] = true + } + } + } + } + + // find children that match the parent filter + var rows []*resourcepb.ResourceTableRow + for i := range m.folders { + meta, err := utils.MetaAccessor(&m.folders[i]) + if err != nil { + continue + } + parentUID := meta.GetFolder() + if parentSet[parentUID] { + rows = append(rows, &resourcepb.ResourceTableRow{ + Key: &resourcepb.ResourceKey{Name: m.folders[i].Name}, + }) + } + } + + return &resourcepb.ResourceSearchResponse{ + Results: &resourcepb.ResourceTable{Rows: rows}, + }, nil } // RebuildIndexes implements resourcepb.ResourceIndexClient. diff --git a/pkg/services/folder/folderimpl/folder_unifiedstorage.go b/pkg/services/folder/folderimpl/folder_unifiedstorage.go index 9b238b769fe..69b8cb59311 100644 --- a/pkg/services/folder/folderimpl/folder_unifiedstorage.go +++ b/pkg/services/folder/folderimpl/folder_unifiedstorage.go @@ -726,19 +726,6 @@ func (s *Service) moveOnApiServer(ctx context.Context, cmd *folder.MoveFolderCom return nil, folder.ErrBadRequest.Errorf("k6 project may not be moved") } - f, err := s.unifiedStore.Get(ctx, folder.GetFolderQuery{ - UID: &cmd.UID, - OrgID: cmd.OrgID, - SignedInUser: cmd.SignedInUser, - }) - if err != nil { - return nil, err - } - - if f != nil && f.ParentUID == accesscontrol.K6FolderUID { - return nil, folder.ErrBadRequest.Errorf("k6 project may not be moved") - } - // Check that the user is allowed to move the folder to the destination folder hasAccess, evalErr := s.canMoveViaApiServer(ctx, cmd) if evalErr != nil { @@ -748,30 +735,7 @@ func (s *Service) moveOnApiServer(ctx context.Context, cmd *folder.MoveFolderCom return nil, dashboards.ErrFolderAccessDenied } - // here we get the folder, we need to get the height of current folder - // and the depth of the new parent folder, the sum can't bypass 8 - folderHeight, err := s.unifiedStore.GetHeight(ctx, cmd.UID, cmd.OrgID, &cmd.NewParentUID) - if err != nil { - return nil, err - } - parents, err := s.unifiedStore.GetParents(ctx, folder.GetParentsQuery{UID: cmd.NewParentUID, OrgID: cmd.OrgID}) - if err != nil { - return nil, err - } - - // height of the folder that is being moved + this current folder itself + depth of the NewParent folder should be less than or equal MaxNestedFolderDepth - if folderHeight+len(parents)+1 > folder.MaxNestedFolderDepth { - return nil, folder.ErrMaximumDepthReached.Errorf("failed to move folder") - } - - for _, parent := range parents { - // if the current folder is already a parent of newparent, we should return error - if parent.UID == cmd.UID { - return nil, folder.ErrCircularReference.Errorf("failed to move folder") - } - } - - f, err = s.unifiedStore.Update(ctx, folder.UpdateFolderCommand{ + f, err := s.unifiedStore.Update(ctx, folder.UpdateFolderCommand{ UID: cmd.UID, OrgID: cmd.OrgID, NewParentUID: &cmd.NewParentUID, From 3522efdf3223762660b442325043daf902b51db9 Mon Sep 17 00:00:00 2001 From: Adela Almasan <88068998+adela-almasan@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:22:26 -0600 Subject: [PATCH 271/297] VizSuggestions: Error handling (#115428) * error handling * retry fetching suggestions * add translation * useAsyncRetry * hasError test * update error handling * clean up the text panel stuff for the current version * cleanup for loop * some more tests for some failure cases * fix lint issue --------- Co-authored-by: Paul Marbach --- .../panel-edit/PanelEditor.tsx | 2 +- .../VisualizationSuggestions.tsx | 194 ++++++++++-------- .../app/features/panel/suggestions/consts.ts | 1 + .../suggestions/getAllSuggestions.test.ts | 113 +++++++--- .../panel/suggestions/getAllSuggestions.ts | 97 +++++---- .../app/features/plugins/importPanelPlugin.ts | 6 + public/app/plugins/panel/table/suggestions.ts | 3 +- public/app/plugins/panel/text/module.tsx | 9 +- public/app/plugins/panel/text/plugin.json | 2 +- public/locales/en-US/grafana.json | 7 +- 10 files changed, 284 insertions(+), 150 deletions(-) diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx index 85813a99f6d..e656a39e6a1 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelEditor.tsx @@ -120,7 +120,7 @@ export class PanelEditor extends SceneObjectBase { dataObject.subscribeToState(async () => { const { data } = dataObject.state; if (hasData(data) && panel.state.pluginId === UNCONFIGURED_PANEL_PLUGIN_ID) { - const suggestions = await getAllSuggestions(data); + const { suggestions } = await getAllSuggestions(data); if (suggestions.length > 0) { const defaultFirstSuggestion = suggestions[0]; diff --git a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx index e93e74f358d..d1763ad835f 100644 --- a/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx +++ b/public/app/features/panel/components/VizTypePicker/VisualizationSuggestions.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/css'; import { Fragment, useState, useEffect, useCallback, useMemo } from 'react'; -import { useAsync, useMeasure } from 'react-use'; +import { useAsyncRetry, useMeasure } from 'react-use'; import { GrafanaTheme2, @@ -28,19 +28,23 @@ export interface Props { panel?: PanelModel; } +const useSuggestions = (data: PanelData | undefined) => { + const [hasFetched, setHasFetched] = useState(false); + const { value, loading, error, retry } = useAsyncRetry(async () => { + await new Promise((resolve) => setTimeout(resolve, hasFetched ? 75 : 0)); + setHasFetched(true); + return await getAllSuggestions(data); + }, [hasFetched, data]); + return { value, loading, error, retry }; +}; + export function VisualizationSuggestions({ onChange, data, panel }: Props) { const styles = useStyles2(getStyles); - const { - value: suggestions, - loading, - error, - } = useAsync(async () => { - if (!hasData(data)) { - return []; - } - return await getAllSuggestions(data); - }, [data]); + const { value: result, loading, error, retry } = useSuggestions(data); + + const suggestions = result?.suggestions; + const hasLoadingErrors = result?.hasErrors ?? false; const [suggestionHash, setSuggestionHash] = useState(null); const [firstCardRef, { width }] = useMeasure(); const [firstCardHash, setFirstCardHash] = useState(null); @@ -131,80 +135,97 @@ export function VisualizationSuggestions({ onChange, data, panel }: Props) { } return ( -
- {isNewVizSuggestionsEnabled - ? suggestionsByVizType.map(([vizType, vizTypeSuggestions], groupIndex) => ( - -
- - {vizType?.info && } - {vizType?.name || t('panel.visualization-suggestions.unknown-viz-type', 'Unknown visualization type')} - -
- {vizTypeSuggestions?.map((suggestion, index) => { - const isCardSelected = suggestionHash === suggestion.hash; - return ( -
{ - if (ev.key === 'Enter' || ev.key === ' ') { - ev.preventDefault(); - applySuggestion(suggestion, isNewVizSuggestionsEnabled && !isCardSelected); - } - }} - ref={index === 0 ? firstCardRef : undefined} - > - {isCardSelected && ( - +
+ + )} +
+ {isNewVizSuggestionsEnabled + ? suggestionsByVizType.map(([vizType, vizTypeSuggestions], groupIndex) => ( + +
+ + {vizType?.info && } + {vizType?.name || + t('panel.visualization-suggestions.unknown-viz-type', 'Unknown visualization type')} + +
+ {vizTypeSuggestions?.map((suggestion, index) => { + const isCardSelected = suggestionHash === suggestion.hash; + return ( +
{ + if (ev.key === 'Enter' || ev.key === ' ') { + ev.preventDefault(); + applySuggestion(suggestion, isNewVizSuggestionsEnabled && !isCardSelected); } - > - {t('panel.visualization-suggestions.use-this-suggestion', 'Use this suggestion')} - - )} - applySuggestion(suggestion, true)} - /> -
- ); - })} -
- )) - : suggestions?.map((suggestion, index) => ( -
- applySuggestion(suggestion)} - /> -
- ))} -
+ }} + ref={index === 0 ? firstCardRef : undefined} + > + {isCardSelected && ( + + )} + applySuggestion(suggestion, true)} + /> +
+ ); + })} + + )) + : suggestions?.map((suggestion, index) => ( +
+ applySuggestion(suggestion)} + /> +
+ ))} +
+ ); } @@ -217,6 +238,11 @@ const getStyles = (theme: GrafanaTheme2) => { width: '100%', marginTop: theme.spacing(6), }), + alertContent: css({ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + }), filterRow: css({ display: 'flex', flexDirection: 'row', diff --git a/public/app/features/panel/suggestions/consts.ts b/public/app/features/panel/suggestions/consts.ts index 76e012a7425..decdf8ac7b4 100644 --- a/public/app/features/panel/suggestions/consts.ts +++ b/public/app/features/panel/suggestions/consts.ts @@ -16,4 +16,5 @@ export const panelsToCheckFirst = [ 'heatmap', 'histogram', 'geomap', + 'text', ]; diff --git a/public/app/features/panel/suggestions/getAllSuggestions.test.ts b/public/app/features/panel/suggestions/getAllSuggestions.test.ts index e76a7df15e3..8657824cc27 100644 --- a/public/app/features/panel/suggestions/getAllSuggestions.test.ts +++ b/public/app/features/panel/suggestions/getAllSuggestions.test.ts @@ -1,4 +1,5 @@ import { + AppEvents, DataFrame, FieldType, getDefaultTimeRange, @@ -18,10 +19,20 @@ import { StackingMode, VizOrientation, } from '@grafana/schema'; +import { appEvents } from 'app/core/app_events'; import { config } from 'app/core/config'; +import { clearPanelPluginCache } from 'app/features/plugins/importPanelPlugin'; +import { pluginImporter } from 'app/features/plugins/importer/pluginImporter'; import { panelsToCheckFirst } from './consts'; -import { getAllSuggestions, sortSuggestions } from './getAllSuggestions'; +import { getAllSuggestions, loadPlugins, sortSuggestions } from './getAllSuggestions'; + +jest.mock('app/core/app_events', () => ({ + appEvents: { + subscribe: jest.fn(() => ({ unsubscribe: jest.fn() })), + publish: jest.fn(), + }, +})); config.featureToggles.externalVizSuggestions = true; @@ -52,28 +63,6 @@ for (const pluginId of panelsToCheckFirst) { }; } -config.panels.text = { - id: 'text', - module: 'core:plugin/text', - sort: idx++, - name: 'Text', - type: PluginType.panel, - baseUrl: 'public/app/plugins/panel', - skipDataQuery: true, - suggestions: false, - info: { - version: '1.0.0', - updated: '2025-01-01', - links: [], - screenshots: [], - author: { - name: 'Grafana Labs', - }, - description: 'Text panel', - logos: { small: 'small/logo', large: 'large/logo' }, - }, -}; - jest.mock('../state/util', () => { const originalModule = jest.requireActual('../state/util'); return { @@ -103,7 +92,8 @@ class ScenarioContext { timeRange: getDefaultTimeRange(), }; - this.suggestions = await getAllSuggestions(panelData); + const result = await getAllSuggestions(panelData); + this.suggestions = result.suggestions; } names() { @@ -554,6 +544,81 @@ describe('sortSuggestions', () => { }); }); +describe('Visualization suggestions error handling', () => { + it('returns result with hasErrors flag', async () => { + const result = await getAllSuggestions({ + series: [ + toDataFrame({ + fields: [ + { name: 'Time', type: FieldType.time, values: [1, 2] }, + { name: 'Max', type: FieldType.number, values: [1, 10] }, + ], + }), + ], + state: LoadingState.Done, + timeRange: getDefaultTimeRange(), + }); + + expect(result).toHaveProperty('suggestions'); + expect(result).toHaveProperty('hasErrors'); + expect(result.hasErrors).toBe(false); + }); +}); + +// this needs to happen before any +describe('loadPlugins', () => { + beforeEach(() => { + clearPanelPluginCache(); + }); + + afterEach(() => { + if (jest.isMockFunction(pluginImporter.importPanel)) { + jest.mocked(pluginImporter.importPanel).mockRestore(); + } + }); + + it('should swallow errors when failing to load core plugins', async () => { + jest.spyOn(console, 'error').mockImplementation(); + + const _importPanel = pluginImporter.importPanel; + jest.spyOn(pluginImporter, 'importPanel').mockImplementation(async (meta) => { + if (meta.id === 'timeseries') { + throw new Error('Failed to load core panel plugin'); + } + return await _importPanel(meta); + }); + + const panelIds = ['timeseries', 'table']; + const { plugins, hasErrors } = await loadPlugins(panelIds); + + expect(plugins).toEqual([expect.objectContaining({ meta: expect.objectContaining({ id: 'table' }) })]); + expect(hasErrors).toBe(true); + expect(appEvents.publish).not.toHaveBeenCalled(); + }); + + it('should swallow errors when failing to load external plugins', async () => { + jest.spyOn(console, 'error').mockImplementation(); + + const panelIds = ['non-existent-panel']; + const { plugins, hasErrors } = await loadPlugins(panelIds); + + expect(plugins).toEqual([]); + expect(hasErrors).toBe(false); + expect(appEvents.publish).toHaveBeenCalledWith({ + type: AppEvents.alertError.name, + payload: [expect.stringContaining('Failed to load panel plugin: non-existent-panel.')], + }); + }); + + it('should load panel plugins with suggestions', async () => { + const panelIds = ['timeseries', 'table']; + const { plugins, hasErrors } = await loadPlugins(panelIds); + + expect(plugins.map((p) => p.meta.id)).toEqual(expect.arrayContaining(['timeseries', 'table'])); + expect(hasErrors).toBe(false); + }); +}); + function repeatFrame(count: number, frame: DataFrame): DataFrame[] { const frames: DataFrame[] = []; for (let i = 0; i < count; i++) { diff --git a/public/app/features/panel/suggestions/getAllSuggestions.ts b/public/app/features/panel/suggestions/getAllSuggestions.ts index ea94add39ab..d0bb73dfb83 100644 --- a/public/app/features/panel/suggestions/getAllSuggestions.ts +++ b/public/app/features/panel/suggestions/getAllSuggestions.ts @@ -1,4 +1,5 @@ import { + AppEvents, getPanelDataSummary, PanelData, PanelDataSummary, @@ -7,41 +8,67 @@ import { PreferredVisualisationType, VisualizationSuggestionScore, } from '@grafana/data'; +import { t } from '@grafana/i18n'; import { config } from '@grafana/runtime'; +import { appEvents } from 'app/core/app_events'; import { importPanelPlugin, isBuiltInPlugin } from 'app/features/plugins/importPanelPlugin'; import { getAllPanelPluginMeta } from '../state/util'; import { panelsToCheckFirst } from './consts'; -/** - * gather and cache the plugins which provide visualization suggestions so they can be invoked to build suggestions - */ -async function getPanelsWithSuggestions(): Promise { - // list of plugins to load is determined by the feature flag - const pluginIds: string[] = config.featureToggles.externalVizSuggestions +interface PluginLoadResult { + plugins: PanelPlugin[]; + hasErrors: boolean; +} + +function getPanelPluginIds(): string[] { + return config.featureToggles.externalVizSuggestions ? getAllPanelPluginMeta() .filter((panel) => panel.suggestions) .map((m) => m.id) : panelsToCheckFirst; +} +/** + * gather and cache the plugins which provide visualization suggestions so they can be invoked to build suggestions + */ +export async function loadPlugins(pluginIds: string[]): Promise { // import the plugins in parallel using Promise.allSettled const plugins: PanelPlugin[] = []; - const settledPromises = await Promise.allSettled(pluginIds.map((id) => importPanelPlugin(id))); + let hasErrors = false; + const settledPromises = await Promise.allSettled( + pluginIds.map(async (pluginId) => { + return await importPanelPlugin(pluginId); + }) + ); + for (let i = 0; i < settledPromises.length; i++) { const settled = settledPromises[i]; - if (settled.status === 'fulfilled') { plugins.push(settled.value); + } else { + const pluginId = pluginIds[i]; + console.error(`Failed to load ${pluginId} for visualization suggestions:`, settled.reason); + + if (isBuiltInPlugin(pluginId)) { + hasErrors = true; + } else { + appEvents.publish({ + type: AppEvents.alertError.name, + payload: [ + t( + 'panel.visualization-suggestions.error-loading-suggestions.plugin-failed', + 'Failed to load panel plugin: {{ pluginId }}.', + { pluginId } + ), + ], + }); + } } - // TODO: do we want to somehow log if there were errors loading some of the plugins? } - if (plugins.length === 0) { - throw new Error('No panel plugins with visualization suggestions found'); - } - - return plugins; + return { plugins, hasErrors }; } /** @@ -89,41 +116,37 @@ export function sortSuggestions(suggestions: PanelPluginVisualizationSuggestion[ }); } +export interface SuggestionsResult { + suggestions: PanelPluginVisualizationSuggestion[]; + hasErrors: boolean; +} + /** * given PanelData, return a sorted list of Suggestions from all plugins which support it. * @param {PanelData} data queried and transformed data for the panel - * @returns {PanelPluginVisualizationSuggestion[]} sorted list of suggestions + * @returns {SuggestionsResult} sorted list of suggestions and error status */ -export async function getAllSuggestions(data?: PanelData): Promise { +export async function getAllSuggestions(data?: PanelData): Promise { const dataSummary = getPanelDataSummary(data?.series); const list: PanelPluginVisualizationSuggestion[] = []; - for (const plugin of await getPanelsWithSuggestions()) { - const suggestions = plugin.getSuggestions(dataSummary); - if (suggestions) { - list.push(...suggestions); - } - } + const pluginIds: string[] = getPanelPluginIds(); + const { plugins, hasErrors: pluginLoadErrors } = await loadPlugins(pluginIds); - if (dataSummary.fieldCount === 0) { - for (const plugin of Object.values(config.panels)) { - if (!plugin.skipDataQuery || plugin.hideFromList) { - continue; + let pluginSuggestionsError = false; + for (const plugin of plugins) { + try { + const suggestions = plugin.getSuggestions(dataSummary); + if (suggestions) { + list.push(...suggestions); } - - list.push({ - name: plugin.name, - pluginId: plugin.id, - description: plugin.info.description, - hash: 'plugin-empty-' + plugin.id, - cardOptions: { - imgSrc: plugin.info.logos.small, - }, - }); + } catch (e) { + console.warn(`error when loading suggestions from plugin "${plugin.meta.id}"`, e); + pluginSuggestionsError = true; } } sortSuggestions(list, dataSummary); - return list; + return { suggestions: list, hasErrors: pluginLoadErrors || pluginSuggestionsError }; } diff --git a/public/app/features/plugins/importPanelPlugin.ts b/public/app/features/plugins/importPanelPlugin.ts index e541b8f7893..ef8d955f562 100644 --- a/public/app/features/plugins/importPanelPlugin.ts +++ b/public/app/features/plugins/importPanelPlugin.ts @@ -62,3 +62,9 @@ export function syncGetPanelPlugin(id: string): PanelPlugin | undefined { function getPanelPlugin(meta: PanelPluginMeta): Promise { return pluginImporter.importPanel(meta); } + +export function clearPanelPluginCache(): void { + for (const key of Object.keys(promiseCache)) { + delete promiseCache[key]; + } +} diff --git a/public/app/plugins/panel/table/suggestions.ts b/public/app/plugins/panel/table/suggestions.ts index 8e0d6e44953..260e73b43eb 100644 --- a/public/app/plugins/panel/table/suggestions.ts +++ b/public/app/plugins/panel/table/suggestions.ts @@ -1,4 +1,5 @@ import { PanelDataSummary, VisualizationSuggestionScore, VisualizationSuggestionsSupplier } from '@grafana/data'; +import { config } from 'app/core/config'; import icnTablePanelSvg from 'app/plugins/panel/table/img/icn-table-panel.svg'; import { Options, FieldConfig } from './panelcfg.gen'; @@ -29,7 +30,7 @@ export const tableSuggestionsSupplier: VisualizationSuggestionsSupplier(TextPanel) defaultValue: defaultOptions.content, }); }) - .setMigrationHandler(textPanelMigrationHandler); + .setMigrationHandler(textPanelMigrationHandler) + .setSuggestionsSupplier((ds) => + ds.fieldCount === 0 && !config.featureToggles.newVizSuggestions + ? [{ cardOptions: { imgSrc: icnTextPanelSvg } }] + : [] + ); diff --git a/public/app/plugins/panel/text/plugin.json b/public/app/plugins/panel/text/plugin.json index ce437e97bf5..a1acce42c6e 100644 --- a/public/app/plugins/panel/text/plugin.json +++ b/public/app/plugins/panel/text/plugin.json @@ -2,7 +2,7 @@ "type": "panel", "name": "Text", "id": "text", - + "suggestions": true, "skipDataQuery": true, "info": { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 9678b91e619..a4e39d534a3 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -11216,9 +11216,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "Apply {{suggestionName}} visualization", + "error-loading-some-suggestions": { + "message": "Some suggestions could not be loaded" + }, "error-loading-suggestions": { "message": "An error occurred when loading visualization suggestions.", - "title": "Error" + "plugin-failed": "Failed to load panel plugin: {{ pluginId }}.", + "title": "Error", + "try-again-button": "Try again" }, "unknown-viz-type": "Unknown visualization type", "use-this-suggestion": "Use this suggestion" From 0284d1e669cee4569e1cc33c11467a122da6955d Mon Sep 17 00:00:00 2001 From: Renato Costa <103441181+renatolabs@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:35:22 -0500 Subject: [PATCH 272/297] unified-storage: add `UnixTimestamp` support to the sqlkv implementation (#115651) * unified-storage: add `UnixTimestamp` support to sqlkv implementation * unified-storage: improve tests and enable all of them on sqlkv --- pkg/storage/unified/resource/sqlkv.go | 3 +- pkg/storage/unified/testing/kv.go | 264 +++++++++++++------------ pkg/storage/unified/testing/kv_test.go | 8 +- 3 files changed, 143 insertions(+), 132 deletions(-) diff --git a/pkg/storage/unified/resource/sqlkv.go b/pkg/storage/unified/resource/sqlkv.go index 9cc2cc32dd0..6d406294a96 100644 --- a/pkg/storage/unified/resource/sqlkv.go +++ b/pkg/storage/unified/resource/sqlkv.go @@ -11,6 +11,7 @@ import ( "iter" "strings" "text/template" + "time" "github.com/google/uuid" "github.com/grafana/grafana/pkg/storage/unified/sql/db" @@ -556,7 +557,7 @@ func (k *sqlKV) BatchDelete(ctx context.Context, section string, keys []string) } func (k *sqlKV) UnixTimestamp(ctx context.Context) (int64, error) { - panic("not implemented!") + return time.Now().Unix(), nil } func closeRows[T any](rows db.Rows, yield func(T, error) bool) { diff --git a/pkg/storage/unified/testing/kv.go b/pkg/storage/unified/testing/kv.go index d1900c7a46e..2031a3e38b2 100644 --- a/pkg/storage/unified/testing/kv.go +++ b/pkg/storage/unified/testing/kv.go @@ -148,13 +148,15 @@ func runTestKVGet(t *testing.T, kv resource.KV, nsPrefix string) { func runTestKVSave(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(30*time.Second)) + nsPrefix += "-save" t.Run("save new key", func(t *testing.T) { + newKey := namespacedKey(nsPrefix, "new-key") testValue := "new test value" - saveKVHelper(t, kv, ctx, testSection, "new-key", strings.NewReader(testValue)) + saveKVHelper(t, kv, ctx, testSection, newKey, strings.NewReader(testValue)) // Verify it was saved - reader, err := kv.Get(ctx, testSection, "new-key") + reader, err := kv.Get(ctx, testSection, newKey) require.NoError(t, err) value, err := io.ReadAll(reader) @@ -165,15 +167,17 @@ func runTestKVSave(t *testing.T, kv resource.KV, nsPrefix string) { }) t.Run("save overwrite existing key", func(t *testing.T) { + overwriteKey := namespacedKey(nsPrefix, "overwrite-key") + // First save - saveKVHelper(t, kv, ctx, testSection, "overwrite-key", strings.NewReader("old value")) + saveKVHelper(t, kv, ctx, testSection, overwriteKey, strings.NewReader("old value")) // Overwrite newValue := "new value" - saveKVHelper(t, kv, ctx, testSection, "overwrite-key", strings.NewReader(newValue)) + saveKVHelper(t, kv, ctx, testSection, overwriteKey, strings.NewReader(newValue)) // Verify it was updated - reader, err := kv.Get(ctx, testSection, "overwrite-key") + reader, err := kv.Get(ctx, testSection, overwriteKey) require.NoError(t, err) value, err := io.ReadAll(reader) @@ -185,15 +189,17 @@ func runTestKVSave(t *testing.T, kv resource.KV, nsPrefix string) { t.Run("save overwrite existing key (datastore)", func(t *testing.T) { section := "unified/data" + overwriteKey := namespacedKey(nsPrefix, "overwrite-key") + // First save - saveKVHelper(t, kv, ctx, section, "overwrite-key", strings.NewReader("old value")) + saveKVHelper(t, kv, ctx, section, overwriteKey, strings.NewReader("old value")) // Overwrite newValue := "new value" - saveKVHelper(t, kv, ctx, section, "overwrite-key", strings.NewReader(newValue)) + saveKVHelper(t, kv, ctx, section, overwriteKey, strings.NewReader(newValue)) // Verify it was updated - reader, err := kv.Get(ctx, section, "overwrite-key") + reader, err := kv.Get(ctx, section, overwriteKey) require.NoError(t, err) value, err := io.ReadAll(reader) @@ -210,11 +216,13 @@ func runTestKVSave(t *testing.T, kv resource.KV, nsPrefix string) { }) t.Run("save binary data", func(t *testing.T) { + binaryKey := namespacedKey(nsPrefix, "binary-key") + binaryData := []byte{0x00, 0x01, 0x02, 0x03, 0xFF, 0xFE, 0xFD} - saveKVHelper(t, kv, ctx, testSection, "binary-key", bytes.NewReader(binaryData)) + saveKVHelper(t, kv, ctx, testSection, binaryKey, bytes.NewReader(binaryData)) // Verify binary data - reader, err := kv.Get(ctx, testSection, "binary-key") + reader, err := kv.Get(ctx, testSection, binaryKey) require.NoError(t, err) value, err := io.ReadAll(reader) @@ -225,11 +233,13 @@ func runTestKVSave(t *testing.T, kv resource.KV, nsPrefix string) { }) t.Run("save key with no data", func(t *testing.T) { + emptyKey := namespacedKey(nsPrefix, "empty-key") + // Save a key with empty data - saveKVHelper(t, kv, ctx, testSection, "empty-key", strings.NewReader("")) + saveKVHelper(t, kv, ctx, testSection, emptyKey, strings.NewReader("")) // Verify it was saved with empty data - reader, err := kv.Get(ctx, testSection, "empty-key") + reader, err := kv.Get(ctx, testSection, emptyKey) require.NoError(t, err) value, err := io.ReadAll(reader) @@ -495,128 +505,134 @@ func runTestKVKeysWithSort(t *testing.T, kv resource.KV, nsPrefix string) { func runTestKVConcurrent(t *testing.T, kv resource.KV, nsPrefix string) { ctx := testutil.NewTestContext(t, time.Now().Add(60*time.Second)) - section := nsPrefix + "-concurrent" + nsPrefix += "-concurrent" - t.Run("concurrent save and get operations", func(t *testing.T) { - const numGoroutines = 10 - const numOperations = 20 + // Test concurrent operations for both sections, as they have different behaviours + // in the sqlkv implementation. + for _, testSection := range []string{"unified/data", "unified/events"} { + t.Run(testSection, func(t *testing.T) { + t.Run("concurrent save and get operations", func(t *testing.T) { + const numGoroutines = 10 + const numOperations = 20 - done := make(chan error, numGoroutines) + done := make(chan error, numGoroutines) - for i := 0; i < numGoroutines; i++ { - go func(goroutineID int) { - var err error - defer func() { done <- err }() + for goroutineID := range numGoroutines { + go func() { + var err error + defer func() { done <- err }() - for j := 0; j < numOperations; j++ { - key := fmt.Sprintf("concurrent-key-%d-%d", goroutineID, j) - value := fmt.Sprintf("concurrent-value-%d-%d", goroutineID, j) + for j := range numOperations { + key := namespacedKey(nsPrefix, fmt.Sprintf("concurrent-key-%d-%d", goroutineID, j)) + value := fmt.Sprintf("concurrent-value-%d-%d", goroutineID, j) - // Save - writer, err := kv.Save(ctx, section, key) - if err != nil { - return - } - defer func() { - err := writer.Close() - require.NoError(t, err) + // Save + writer, err := kv.Save(ctx, testSection, key) + if err != nil { + return + } + defer func() { + err := writer.Close() + require.NoError(t, err) + }() + _, err = io.Copy(writer, strings.NewReader(value)) + if err != nil { + return + } + err = writer.Close() + if err != nil { + return + } + + // Get immediately + reader, err := kv.Get(ctx, testSection, key) + if err != nil { + return + } + + readValue, err := io.ReadAll(reader) + require.NoError(t, err) + err = reader.Close() + require.NoError(t, err) + assert.Equal(t, value, string(readValue)) + } }() - _, err = io.Copy(writer, strings.NewReader(value)) - if err != nil { - return - } - err = writer.Close() - if err != nil { - return - } + } - // Get immediately - reader, err := kv.Get(ctx, section, key) - if err != nil { - return - } - - readValue, err := io.ReadAll(reader) + // Wait for all goroutines to complete + for range numGoroutines { + err := <-done require.NoError(t, err) - err = reader.Close() + } + }) + + t.Run("concurrent save, delete, and list operations", func(t *testing.T) { + const numGoroutines = 5 + done := make(chan error, numGoroutines) + + for i := range numGoroutines { + go func(goroutineID int) { + var err error + defer func() { done <- err }() + + key := namespacedKey(nsPrefix, fmt.Sprintf("concurrent-ops-key-%d", goroutineID)) + value := fmt.Sprintf("concurrent-ops-value-%d", goroutineID) + + // Save + writer, err := kv.Save(ctx, testSection, key) + if err != nil { + return + } + defer func() { + err := writer.Close() + require.NoError(t, err) + }() + _, err = io.Copy(writer, strings.NewReader(value)) + if err != nil { + return + } + err = writer.Close() + if err != nil { + return + } + + // List to verify it exists + found := false + for k, err := range kv.Keys(ctx, testSection, resource.ListOptions{}) { + if err != nil { + return + } + if k == key { + found = true + break + } + } + if !found { + err = fmt.Errorf("key %s not found in list", key) + return + } + + // Delete + err = kv.Delete(ctx, testSection, key) + if err != nil { + return + } + + // Verify it's deleted + _, err = kv.Get(ctx, testSection, key) + require.ErrorIs(t, resource.ErrNotFound, err) + err = nil // Expected error, so clear it + }(i) + } + + // Wait for all goroutines to complete + for range numGoroutines { + err := <-done require.NoError(t, err) - assert.Equal(t, value, string(readValue)) } - }(i) - } - - // Wait for all goroutines to complete - for i := 0; i < numGoroutines; i++ { - err := <-done - require.NoError(t, err) - } - }) - - t.Run("concurrent save, delete, and list operations", func(t *testing.T) { - const numGoroutines = 5 - done := make(chan error, numGoroutines) - - for i := 0; i < numGoroutines; i++ { - go func(goroutineID int) { - var err error - defer func() { done <- err }() - - key := fmt.Sprintf("concurrent-ops-key-%d", goroutineID) - value := fmt.Sprintf("concurrent-ops-value-%d", goroutineID) - - // Save - writer, err := kv.Save(ctx, section, key) - if err != nil { - return - } - defer func() { - err := writer.Close() - require.NoError(t, err) - }() - _, err = io.Copy(writer, strings.NewReader(value)) - if err != nil { - return - } - err = writer.Close() - if err != nil { - return - } - - // List to verify it exists - found := false - for k, err := range kv.Keys(ctx, section, resource.ListOptions{}) { - if err != nil { - return - } - if k == key { - found = true - break - } - } - if !found { - err = fmt.Errorf("key %s not found in list", key) - return - } - - // Delete - err = kv.Delete(ctx, section, key) - if err != nil { - return - } - - // Verify it's deleted - _, err = kv.Get(ctx, section, key) - require.ErrorIs(t, resource.ErrNotFound, err) - err = nil // Expected error, so clear it - }(i) - } - - // Wait for all goroutines to complete - for i := 0; i < numGoroutines; i++ { - err := <-done - require.NoError(t, err) - } - }) + }) + }) + } } func runTestKVUnixTimestamp(t *testing.T, kv resource.KV, nsPrefix string) { diff --git a/pkg/storage/unified/testing/kv_test.go b/pkg/storage/unified/testing/kv_test.go index 5e94ccd8a7f..af7de65e52c 100644 --- a/pkg/storage/unified/testing/kv_test.go +++ b/pkg/storage/unified/testing/kv_test.go @@ -44,11 +44,5 @@ func TestSQLKV(t *testing.T) { kv, err := resource.NewSQLKV(eDB) require.NoError(t, err) return kv - }, &KVTestOptions{ - NSPrefix: "sql-kv-test", - SkipTests: map[string]bool{ - TestKVConcurrent: true, - TestKVUnixTimestamp: true, - }, - }) + }, &KVTestOptions{NSPrefix: "sql-kv-test"}) } From 8cfac85b48145f485b2caac2cac2721e352717e9 Mon Sep 17 00:00:00 2001 From: Paul Marbach Date: Fri, 19 Dec 2025 15:41:57 -0500 Subject: [PATCH 273/297] Gauge: Add guide dots for rounded bars to help with accuracy, update color logic for more consistent gradients (#115285) * Gauge: Fit-and-finish tweaks to glows, text position, and sparkline size * adjust text height and positions a little more * cohesive no data handling * more tweaks * fix migration test * Fix JSON formatting by adding missing newline * remove new line * Gauge: Add guide dots for rounded bars to help with accuracy * 30% width * remove spotlight, starting to make gradients a bit more predictable * fix segmented * update rotation of gauge color * update i18n and migration tests * fix spacing * more fixture updates * wip: using clip-path and CSS for drawing the gauge * wip: overhaul color in gauge * wip: progress on everything * refactoring defs into utils * its all working * fixme comment * fix backend migration tests * remove any other mentions of spotlights * one more tweak * update gdev * add lots of tests and reorganize the code a bit * fix dev dashboard fixture * more cleanup, optimization * fix a couple of bugs * fix bad import * disable storybook test due to false positive * a more sweeping disable of the color-contrast * update backend tests * update gradient for fixed color * test all dark/light theme variants * set opacity to 0.5 for dots * move min degrees for start dot render to a const * change endpoint marks to be configurable * update gdev and fixtures * i18n * shore up testing a bit * remove period for consistency * hide glow at small angles * more testing and cleanup * addressing PR comments * Update packages/grafana-ui/src/components/RadialGauge/colors.ts Co-authored-by: Jesse David Peterson * Update packages/grafana-ui/src/components/RadialGauge/colors.ts Co-authored-by: Jesse David Peterson * break out binary search stuff and write tests * fix lint issues --------- Co-authored-by: Jesse David Peterson --- .../v0alpha1.gauge_tests_new.v42.json | 171 ++------- .../v0alpha1.gauge_tests_old_to_new.v42.json | 2 - .../v0alpha1.gauge_tests_new.v42.v1beta1.json | 221 +++--------- ...v0alpha1.gauge_tests_new.v42.v2alpha1.json | 247 +++---------- .../v0alpha1.gauge_tests_new.v42.v2beta1.json | 250 +++---------- ...a1.gauge_tests_old_to_new.v42.v1beta1.json | 6 +- ...1.gauge_tests_old_to_new.v42.v2alpha1.json | 6 +- ...a1.gauge_tests_old_to_new.v42.v2beta1.json | 6 +- .../panel-gauge/gauge_tests_new.v42.json | 336 ++++++------------ .../gauge_tests_old_to_new.v42.json | 6 +- .../panel-gauge/gauge_tests_new.json | 335 ++++++----------- .../panel-gauge/gauge_tests_old_to_new.json | 2 - .../panelcfg/x/NewGaugePanelCfg_types.gen.ts | 8 +- .../components/RadialGauge/RadialArcPath.tsx | 185 +++++++--- .../src/components/RadialGauge/RadialBar.tsx | 119 +++---- .../RadialGauge/RadialBarSegmented.tsx | 164 +++------ .../RadialGauge/RadialColorDefs.tsx | 141 -------- .../RadialGauge/RadialGauge.story.tsx | 128 +++---- .../RadialGauge/RadialGauge.test.tsx | 25 +- .../components/RadialGauge/RadialGauge.tsx | 69 ++-- .../RadialGauge/RadialScaleLabels.tsx | 119 +++---- .../RadialGauge/RadialSparkline.tsx | 99 ++++-- .../src/components/RadialGauge/RadialText.tsx | 222 ++++++------ .../components/RadialGauge/ThresholdsBar.tsx | 42 +-- .../__snapshots__/colors.test.ts.snap | 144 ++++++++ .../__snapshots__/utils.test.ts.snap | 17 + .../src/components/RadialGauge/colors.test.ts | 306 ++++++++++++++++ .../src/components/RadialGauge/colors.ts | 195 ++++++++++ .../src/components/RadialGauge/effects.tsx | 89 +++-- .../src/components/RadialGauge/types.ts | 25 ++ .../src/components/RadialGauge/utils.test.ts | 197 +++++++++- .../src/components/RadialGauge/utils.ts | 171 +++++++-- .../plugins/panel/radialbar/EffectsEditor.tsx | 11 - .../panel/radialbar/RadialBarPanel.tsx | 6 +- public/app/plugins/panel/radialbar/module.tsx | 30 ++ .../app/plugins/panel/radialbar/panelcfg.cue | 6 +- .../plugins/panel/radialbar/panelcfg.gen.ts | 8 +- .../plugins/panel/radialbar/suggestions.ts | 13 - public/locales/en-US/grafana.json | 19 +- 39 files changed, 2142 insertions(+), 2004 deletions(-) delete mode 100644 packages/grafana-ui/src/components/RadialGauge/RadialColorDefs.tsx create mode 100644 packages/grafana-ui/src/components/RadialGauge/__snapshots__/colors.test.ts.snap create mode 100644 packages/grafana-ui/src/components/RadialGauge/__snapshots__/utils.test.ts.snap create mode 100644 packages/grafana-ui/src/components/RadialGauge/colors.test.ts create mode 100644 packages/grafana-ui/src/components/RadialGauge/colors.ts create mode 100644 packages/grafana-ui/src/components/RadialGauge/types.ts diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.json index 7c7c479199d..62648d7a4aa 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.json @@ -71,12 +71,11 @@ "id": 1, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": false, "centerGlow": false, - "rounded": true, - "spotlight": false, "gradient": false }, "orientation": "auto", @@ -150,12 +149,11 @@ "id": 4, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": false, "centerGlow": true, - "rounded": true, - "spotlight": false, "gradient": false }, "orientation": "auto", @@ -229,12 +227,11 @@ "id": 3, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "rounded": true, - "spotlight": false, "gradient": false }, "orientation": "auto", @@ -271,85 +268,6 @@ "title": "Center and bar glow", "type": "radialbar" }, - { - "datasource": { - "type": "grafana-testdata-datasource" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 4, - "x": 12, - "y": 1 - }, - "id": 5, - "maxDataPoints": 20, - "options": { - "barWidthFactor": 0.4, - "effects": { - "barGlow": true, - "centerGlow": true, - "rounded": true, - "spotlight": true, - "gradient": false - }, - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "segmentCount": 1, - "segmentSpacing": 0.3, - "shape": "circle", - "showThresholdLabels": false, - "showThresholdMarkers": false, - "sparkline": false - }, - "pluginVersion": "13.0.0-pre", - "targets": [ - { - "alias": "1", - "datasource": { - "type": "grafana-testdata-datasource" - }, - "max": 100, - "min": 1, - "noise": 22, - "refId": "A", - "scenarioId": "random_walk", - "spread": 22, - "startValue": 1 - } - ], - "title": "Spotlight", - "type": "radialbar" - }, { "datasource": { "type": "grafana-testdata-datasource" @@ -391,10 +309,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "rounded": true, - "spotlight": true, "gradient": false }, + "barShape": "rounded", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -470,10 +387,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "rounded": false, - "spotlight": true, "gradient": false }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -549,10 +465,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "rounded": false, - "spotlight": true, "gradient": false }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -641,10 +556,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "rounded": true, - "spotlight": true, "gradient": false }, + "barShape": "rounded", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -720,10 +634,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "rounded": true, - "spotlight": true, "gradient": false }, + "barShape": "rounded", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -799,10 +712,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "rounded": true, - "spotlight": true, "gradient": false }, + "barShape": "rounded", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -878,10 +790,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "rounded": true, - "spotlight": true, "gradient": false }, + "barShape": "rounded", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -974,10 +885,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "rounded": false, - "spotlight": false, "gradient": false }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1053,10 +963,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "rounded": false, - "spotlight": false, "gradient": false }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1132,10 +1041,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "rounded": false, - "spotlight": false, "gradient": true }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1211,10 +1119,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "rounded": false, - "spotlight": false, "gradient": false }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1290,10 +1197,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "rounded": false, - "spotlight": false, "gradient": false }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1386,10 +1292,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "rounded": false, - "spotlight": false, "gradient": true }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1469,10 +1374,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "rounded": false, - "spotlight": false, "gradient": true }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1552,10 +1456,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "rounded": false, - "spotlight": false, "gradient": true }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1641,13 +1544,13 @@ "options": { "barWidth": 12, "barWidthFactor": 0.4, + "barShape": "rounded", "effects": { "barGlow": true, "centerGlow": true, - "rounded": true, - "spotlight": true, "gradient": true }, + "endpointMarker": "glow", "glow": "both", "orientation": "auto", "reduceOptions": { @@ -1662,8 +1565,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -1730,10 +1632,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "rounded": true, - "spotlight": true, "gradient": true }, + "barShape": "rounded", "glow": "both", "orientation": "auto", "reduceOptions": { @@ -1748,8 +1649,7 @@ "shape": "gauge", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": true, - "spotlight": true + "sparkline": true }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -1830,10 +1730,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "rounded": true, - "spotlight": true, "gradient": true }, + "barShape": "rounded", "glow": "both", "orientation": "auto", "reduceOptions": { @@ -1848,8 +1747,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -1917,9 +1815,6 @@ "effects": { "barGlow": true, "centerGlow": true, - "rounded": true, - "sparkline": false, - "spotlight": true, "gradient": true }, "glow": "both", @@ -1934,10 +1829,10 @@ "segmentCount": 12, "segmentSpacing": 0.3, "shape": "circle", + "barShape": "rounded", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -2004,10 +1899,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "rounded": true, - "spotlight": true, "gradient": true }, + "barShape": "rounded", "glow": "both", "orientation": "auto", "reduceOptions": { @@ -2022,8 +1916,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -2090,10 +1983,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "rounded": true, - "spotlight": true, "gradient": true }, + "barShape": "rounded", "glow": "both", "orientation": "auto", "reduceOptions": { @@ -2108,8 +2000,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.json b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.json index a3de6df336a..dda0fbcd432 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/input/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.json @@ -955,8 +955,6 @@ "effects": { "barGlow": false, "centerGlow": false, - "rounded": false, - "spotlight": false, "gradient": false }, "orientation": "auto", diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v1beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v1beta1.json index 2eeb3040e6d..e04d448a5b8 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v1beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v1beta1.json @@ -77,13 +77,12 @@ "id": 1, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": true, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -156,13 +155,12 @@ "id": 4, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": false, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -235,13 +233,12 @@ "id": 3, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -277,85 +274,6 @@ "title": "Center and bar glow", "type": "radialbar" }, - { - "datasource": { - "type": "grafana-testdata-datasource" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 4, - "x": 12, - "y": 1 - }, - "id": 5, - "maxDataPoints": 20, - "options": { - "barWidthFactor": 0.4, - "effects": { - "barGlow": true, - "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true - }, - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "segmentCount": 1, - "segmentSpacing": 0.3, - "shape": "circle", - "showThresholdLabels": false, - "showThresholdMarkers": false, - "sparkline": false - }, - "pluginVersion": "13.0.0-pre", - "targets": [ - { - "alias": "1", - "datasource": { - "type": "grafana-testdata-datasource" - }, - "max": 100, - "min": 1, - "noise": 22, - "refId": "A", - "scenarioId": "random_walk", - "spread": 22, - "startValue": 1 - } - ], - "title": "Spotlight", - "type": "radialbar" - }, { "datasource": { "type": "grafana-testdata-datasource" @@ -393,13 +311,12 @@ "id": 8, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -472,13 +389,12 @@ "id": 22, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": false, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -551,13 +467,12 @@ "id": 23, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": false, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -643,13 +558,12 @@ "id": 18, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.1, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -722,13 +636,12 @@ "id": 19, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.32, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -801,13 +714,12 @@ "id": 20, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.57, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -880,13 +792,12 @@ "id": 21, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.8, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -976,13 +887,12 @@ "id": 25, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1055,13 +965,12 @@ "id": 26, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1134,13 +1043,12 @@ "id": 29, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -1213,13 +1121,12 @@ "id": 30, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1292,13 +1199,12 @@ "id": 28, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1388,13 +1294,12 @@ "id": 32, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -1471,13 +1376,12 @@ "id": 34, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -1554,13 +1458,12 @@ "id": 33, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -1645,15 +1548,15 @@ "id": 9, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, + "endpointMarker": "glow", "glow": "both", "orientation": "auto", "reduceOptions": { @@ -1668,8 +1571,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -1731,14 +1633,13 @@ "id": 11, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -1754,8 +1655,7 @@ "shape": "gauge", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": true, - "spotlight": true + "sparkline": true }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -1831,14 +1731,13 @@ "id": 13, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.49, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -1854,8 +1753,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -1918,15 +1816,13 @@ "id": 14, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.49, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "sparkline": false, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -1942,8 +1838,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -2005,14 +1900,13 @@ "id": 15, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.84, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -2028,8 +1922,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -2091,14 +1984,13 @@ "id": 16, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.66, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -2114,8 +2006,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -2160,4 +2051,4 @@ "storedVersion": "v0alpha1" } } -} \ No newline at end of file +} diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2alpha1.json index 4aecf4e0d9c..0e6e3e13da5 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2alpha1.json @@ -73,13 +73,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": true, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -165,14 +164,13 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -188,8 +186,7 @@ "shape": "gauge", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": true, - "spotlight": true + "sparkline": true }, "fieldConfig": { "defaults": { @@ -262,14 +259,13 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.49, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -285,8 +281,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "fieldConfig": { "defaults": { @@ -360,15 +355,13 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.49, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "sparkline": false, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -384,8 +377,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "fieldConfig": { "defaults": { @@ -459,14 +451,13 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.84, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -482,8 +473,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "fieldConfig": { "defaults": { @@ -556,14 +546,13 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.66, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -579,8 +568,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "fieldConfig": { "defaults": { @@ -653,13 +641,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "rounded", "barWidthFactor": 0.1, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -745,13 +732,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "rounded", "barWidthFactor": 0.32, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -837,13 +823,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "rounded", "barWidthFactor": 0.57, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -929,13 +914,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "rounded", "barWidthFactor": 0.8, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1021,13 +1005,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": false, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1113,13 +1096,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": false, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1201,13 +1183,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1293,13 +1274,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1385,13 +1365,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1477,13 +1456,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -1573,13 +1551,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1661,13 +1638,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1753,13 +1729,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -1849,13 +1824,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -1945,13 +1919,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -2045,105 +2018,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": false, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": false - }, - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "segmentCount": 1, - "segmentSpacing": 0.3, - "shape": "circle", - "showThresholdLabels": false, - "showThresholdMarkers": false, - "sparkline": false - }, - "fieldConfig": { - "defaults": { - "min": 0, - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "thresholds" - } - }, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Spotlight", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "grafana-testdata-datasource", - "spec": { - "alias": "1", - "max": 100, - "min": 1, - "noise": 22, - "scenarioId": "random_walk", - "spread": 22, - "startValue": 1 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": { - "maxDataPoints": 20 - } - } - }, - "vizConfig": { - "kind": "radialbar", - "spec": { - "pluginVersion": "13.0.0-pre", - "options": { - "barWidthFactor": 0.4, - "effects": { - "barGlow": true, - "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -2229,13 +2109,12 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -2321,15 +2200,15 @@ "spec": { "pluginVersion": "13.0.0-pre", "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, + "endpointMarker": "glow", "glow": "both", "orientation": "auto", "reduceOptions": { @@ -2344,8 +2223,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "fieldConfig": { "defaults": { @@ -2429,19 +2307,6 @@ } } }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 12, - "y": 0, - "width": 4, - "height": 6, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, { "kind": "GridLayoutItem", "spec": { @@ -2826,4 +2691,4 @@ "storedVersion": "v0alpha1" } } -} \ No newline at end of file +} diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2beta1.json index 6b567f19b5e..ad2b8ca0385 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2beta1.json @@ -77,13 +77,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": true, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -172,14 +171,13 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -195,8 +193,7 @@ "shape": "gauge", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": true, - "spotlight": true + "sparkline": true }, "fieldConfig": { "defaults": { @@ -272,14 +269,13 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.49, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -295,8 +291,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "fieldConfig": { "defaults": { @@ -373,15 +368,13 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.49, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "sparkline": false, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -397,8 +390,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "fieldConfig": { "defaults": { @@ -475,14 +467,13 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.84, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -498,8 +489,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "fieldConfig": { "defaults": { @@ -575,14 +565,13 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.66, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -598,8 +587,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "fieldConfig": { "defaults": { @@ -675,13 +663,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "rounded", "barWidthFactor": 0.1, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -770,13 +757,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "rounded", "barWidthFactor": 0.32, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -865,13 +851,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "rounded", "barWidthFactor": 0.57, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -960,13 +945,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "rounded", "barWidthFactor": 0.8, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1055,13 +1039,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": false, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1150,13 +1133,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": false, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1241,13 +1223,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1336,13 +1317,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1431,13 +1411,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1526,13 +1505,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -1625,13 +1603,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1716,13 +1693,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1811,13 +1787,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -1910,13 +1885,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -2009,13 +1983,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -2112,108 +2085,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": false, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": false - }, - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "segmentCount": 1, - "segmentSpacing": 0.3, - "shape": "circle", - "showThresholdLabels": false, - "showThresholdMarkers": false, - "sparkline": false - }, - "fieldConfig": { - "defaults": { - "min": 0, - "max": 100, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "value": 0, - "color": "green" - }, - { - "value": 80, - "color": "red" - } - ] - }, - "color": { - "mode": "thresholds" - } - }, - "overrides": [] - } - } - } - } - }, - "panel-5": { - "kind": "Panel", - "spec": { - "id": 5, - "title": "Spotlight", - "description": "", - "links": [], - "data": { - "kind": "QueryGroup", - "spec": { - "queries": [ - { - "kind": "PanelQuery", - "spec": { - "query": { - "kind": "DataQuery", - "group": "grafana-testdata-datasource", - "version": "v0", - "spec": { - "alias": "1", - "max": 100, - "min": 1, - "noise": 22, - "scenarioId": "random_walk", - "spread": 22, - "startValue": 1 - } - }, - "refId": "A", - "hidden": false - } - } - ], - "transformations": [], - "queryOptions": { - "maxDataPoints": 20 - } - } - }, - "vizConfig": { - "kind": "VizConfig", - "group": "radialbar", - "version": "13.0.0-pre", - "spec": { - "options": { - "barWidthFactor": 0.4, - "effects": { - "barGlow": true, - "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -2302,13 +2179,12 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -2397,15 +2273,15 @@ "version": "13.0.0-pre", "spec": { "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, + "endpointMarker": "glow", "glow": "both", "orientation": "auto", "reduceOptions": { @@ -2420,8 +2296,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "fieldConfig": { "defaults": { @@ -2505,19 +2380,6 @@ } } }, - { - "kind": "GridLayoutItem", - "spec": { - "x": 12, - "y": 0, - "width": 4, - "height": 6, - "element": { - "kind": "ElementReference", - "name": "panel-5" - } - } - }, { "kind": "GridLayoutItem", "spec": { @@ -2902,4 +2764,4 @@ "storedVersion": "v0alpha1" } } -} \ No newline at end of file +} diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v1beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v1beta1.json index 959f0193ad6..1d9f7e56513 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v1beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v1beta1.json @@ -961,9 +961,7 @@ "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1175,4 +1173,4 @@ "storedVersion": "v0alpha1" } } -} \ No newline at end of file +} diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2alpha1.json index 66b29e88d13..7b3f601b5cf 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2alpha1.json @@ -864,9 +864,7 @@ "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1620,4 +1618,4 @@ "storedVersion": "v0alpha1" } } -} \ No newline at end of file +} diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2beta1.json index b870d0a91ad..534e7a1600c 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2beta1.json @@ -901,9 +901,7 @@ "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1672,4 +1670,4 @@ "storedVersion": "v0alpha1" } } -} \ No newline at end of file +} diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_new.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_new.v42.json index a89d8744f39..cb130445efc 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_new.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_new.v42.json @@ -75,10 +75,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": true, - "spotlight": false + "gradient": false }, + "barShape": "rounded", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -154,10 +153,9 @@ "effects": { "barGlow": false, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": false + "gradient": false }, + "barShape": "rounded", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -233,10 +231,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": false + "gradient": false }, + "barShape": "rounded", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -305,85 +302,6 @@ "x": 12, "y": 1 }, - "id": 5, - "maxDataPoints": 20, - "options": { - "barWidthFactor": 0.4, - "effects": { - "barGlow": true, - "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true - }, - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "segmentCount": 1, - "segmentSpacing": 0.3, - "shape": "circle", - "showThresholdLabels": false, - "showThresholdMarkers": false, - "sparkline": false - }, - "pluginVersion": "13.0.0-pre", - "targets": [ - { - "alias": "1", - "datasource": { - "type": "grafana-testdata-datasource" - }, - "max": 100, - "min": 1, - "noise": 22, - "refId": "A", - "scenarioId": "random_walk", - "spread": 22, - "startValue": 1 - } - ], - "title": "Spotlight", - "type": "radialbar" - }, - { - "datasource": { - "type": "grafana-testdata-datasource" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 4, - "x": 16, - "y": 1 - }, "id": 8, "maxDataPoints": 20, "options": { @@ -391,10 +309,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, + "barShape": "rounded", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -460,8 +377,8 @@ "gridPos": { "h": 6, "w": 4, - "x": 0, - "y": 7 + "x": 16, + "y": 1 }, "id": 22, "maxDataPoints": 20, @@ -470,10 +387,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": false, - "spotlight": true + "gradient": false }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -539,8 +455,8 @@ "gridPos": { "h": 6, "w": 4, - "x": 4, - "y": 7 + "x": 20, + "y": 1 }, "id": 23, "maxDataPoints": 20, @@ -549,10 +465,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": false, - "spotlight": true + "gradient": false }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -593,7 +508,7 @@ "h": 1, "w": 24, "x": 0, - "y": 13 + "y": 7 }, "id": 17, "panels": [], @@ -630,9 +545,9 @@ }, "gridPos": { "h": 6, - "w": 5, + "w": 4, "x": 0, - "y": 14 + "y": 8 }, "id": 18, "maxDataPoints": 20, @@ -641,10 +556,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, + "barShape": "rounded", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -709,9 +623,9 @@ }, "gridPos": { "h": 6, - "w": 5, - "x": 5, - "y": 14 + "w": 4, + "x": 4, + "y": 8 }, "id": 19, "maxDataPoints": 20, @@ -720,10 +634,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, + "barShape": "rounded", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -788,9 +701,9 @@ }, "gridPos": { "h": 6, - "w": 5, - "x": 10, - "y": 14 + "w": 4, + "x": 8, + "y": 8 }, "id": 20, "maxDataPoints": 20, @@ -799,10 +712,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, + "barShape": "rounded", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -867,9 +779,9 @@ }, "gridPos": { "h": 6, - "w": 5, - "x": 15, - "y": 14 + "w": 4, + "x": 12, + "y": 8 }, "id": 21, "maxDataPoints": 20, @@ -878,10 +790,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, + "barShape": "rounded", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -922,7 +833,7 @@ "h": 1, "w": 24, "x": 0, - "y": 20 + "y": 14 }, "id": 24, "panels": [], @@ -963,9 +874,9 @@ }, "gridPos": { "h": 6, - "w": 6, + "w": 4, "x": 0, - "y": 21 + "y": 15 }, "id": 25, "maxDataPoints": 20, @@ -974,10 +885,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1042,9 +952,9 @@ }, "gridPos": { "h": 6, - "w": 6, - "x": 6, - "y": 21 + "w": 4, + "x": 4, + "y": 15 }, "id": 26, "maxDataPoints": 20, @@ -1053,10 +963,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1121,9 +1030,9 @@ }, "gridPos": { "h": 6, - "w": 5, - "x": 12, - "y": 21 + "w": 4, + "x": 8, + "y": 15 }, "id": 29, "maxDataPoints": 20, @@ -1132,10 +1041,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1199,10 +1107,10 @@ "overrides": [] }, "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 27 + "h": 6, + "w": 4, + "x": 12, + "y": 15 }, "id": 30, "maxDataPoints": 20, @@ -1211,10 +1119,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1278,10 +1185,10 @@ "overrides": [] }, "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 27 + "h": 6, + "w": 4, + "x": 16, + "y": 15 }, "id": 28, "maxDataPoints": 20, @@ -1290,10 +1197,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1330,7 +1236,7 @@ "h": 1, "w": 24, "x": 0, - "y": 34 + "y": 21 }, "id": 31, "panels": [], @@ -1377,7 +1283,7 @@ "h": 10, "w": 7, "x": 0, - "y": 35 + "y": 22 }, "id": 32, "maxDataPoints": 20, @@ -1386,10 +1292,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1460,7 +1365,7 @@ "h": 10, "w": 7, "x": 7, - "y": 35 + "y": 22 }, "id": 34, "maxDataPoints": 20, @@ -1469,10 +1374,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1543,7 +1447,7 @@ "h": 10, "w": 6, "x": 14, - "y": 35 + "y": 22 }, "id": 33, "maxDataPoints": 20, @@ -1552,10 +1456,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -1592,7 +1495,7 @@ "h": 1, "w": 24, "x": 0, - "y": 45 + "y": 32 }, "id": 6, "panels": [], @@ -1633,20 +1536,20 @@ "h": 6, "w": 24, "x": 0, - "y": 46 + "y": 33 }, "id": 9, "maxDataPoints": 20, "options": { "barWidth": 12, "barWidthFactor": 0.4, + "barShape": "rounded", "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, + "endpointMarker": "glow", "glow": "both", "orientation": "auto", "reduceOptions": { @@ -1661,8 +1564,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -1717,7 +1619,7 @@ "h": 6, "w": 24, "x": 0, - "y": 52 + "y": 39 }, "id": 11, "maxDataPoints": 20, @@ -1727,10 +1629,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, + "barShape": "rounded", "glow": "both", "orientation": "auto", "reduceOptions": { @@ -1745,8 +1646,7 @@ "shape": "gauge", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": true, - "spotlight": true + "sparkline": true }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -1773,7 +1673,7 @@ "h": 1, "w": 24, "x": 0, - "y": 58 + "y": 45 }, "id": 12, "panels": [], @@ -1815,7 +1715,7 @@ "h": 7, "w": 4, "x": 0, - "y": 59 + "y": 46 }, "id": 13, "maxDataPoints": 20, @@ -1825,10 +1725,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, + "barShape": "rounded", "glow": "both", "orientation": "auto", "reduceOptions": { @@ -1843,8 +1742,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -1900,7 +1798,7 @@ "h": 7, "w": 5, "x": 4, - "y": 59 + "y": 46 }, "id": 14, "maxDataPoints": 20, @@ -1910,10 +1808,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, + "barShape": "rounded", "glow": "both", "orientation": "auto", "reduceOptions": { @@ -1928,8 +1825,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -1984,7 +1880,7 @@ "h": 7, "w": 5, "x": 9, - "y": 59 + "y": 46 }, "id": 15, "maxDataPoints": 20, @@ -1994,10 +1890,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, + "barShape": "rounded", "glow": "both", "orientation": "auto", "reduceOptions": { @@ -2012,8 +1907,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -2068,7 +1962,7 @@ "h": 7, "w": 6, "x": 14, - "y": 59 + "y": 46 }, "id": 16, "maxDataPoints": 20, @@ -2078,10 +1972,9 @@ "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, + "barShape": "rounded", "glow": "both", "orientation": "auto", "reduceOptions": { @@ -2096,8 +1989,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -2124,7 +2016,7 @@ "h": 1, "w": 24, "x": 0, - "y": 66 + "y": 53 }, "id": 35, "panels": [], @@ -2155,10 +2047,10 @@ "overrides": [] }, "gridPos": { - "h": 8, - "w": 6, + "h": 5, + "w": 12, "x": 0, - "y": 67 + "y": 54 }, "id": 36, "options": { @@ -2166,10 +2058,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -2223,10 +2114,10 @@ "overrides": [] }, "gridPos": { - "h": 8, - "w": 6, - "x": 6, - "y": 67 + "h": 5, + "w": 12, + "x": 12, + "y": 54 }, "id": 37, "options": { @@ -2234,10 +2125,9 @@ "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, + "barShape": "flat", "orientation": "auto", "reduceOptions": { "calcs": [ @@ -2279,4 +2169,4 @@ "title": "Panel tests - Gauge (new)", "uid": "panel-tests-gauge-new", "weekStart": "" -} \ No newline at end of file +} diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_old_to_new.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_old_to_new.v42.json index 4a5ac97a6b5..dda0fbcd432 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_old_to_new.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-gauge/gauge_tests_old_to_new.v42.json @@ -955,9 +955,7 @@ "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1162,4 +1160,4 @@ "title": "Panel tests - Old gauge to new", "uid": "panel-tests-old-gauge-to-new", "weekStart": "" -} \ No newline at end of file +} diff --git a/devenv/dev-dashboards/panel-gauge/gauge_tests_new.json b/devenv/dev-dashboards/panel-gauge/gauge_tests_new.json index b3c47c9aa7a..65cc0b0a5ae 100644 --- a/devenv/dev-dashboards/panel-gauge/gauge_tests_new.json +++ b/devenv/dev-dashboards/panel-gauge/gauge_tests_new.json @@ -71,13 +71,12 @@ "id": 1, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": true, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -148,13 +147,12 @@ "id": 4, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": false, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -225,13 +223,12 @@ "id": 3, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -299,93 +296,15 @@ "x": 12, "y": 1 }, - "id": 5, - "maxDataPoints": 20, - "options": { - "barWidthFactor": 0.4, - "effects": { - "barGlow": true, - "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true - }, - "orientation": "auto", - "reduceOptions": { - "calcs": ["lastNotNull"], - "fields": "", - "values": false - }, - "segmentCount": 1, - "segmentSpacing": 0.3, - "shape": "circle", - "showThresholdLabels": false, - "showThresholdMarkers": false, - "sparkline": false - }, - "pluginVersion": "13.0.0-pre", - "targets": [ - { - "alias": "1", - "datasource": { - "type": "grafana-testdata-datasource" - }, - "max": 100, - "min": 1, - "noise": 22, - "refId": "A", - "scenarioId": "random_walk", - "spread": 22, - "startValue": 1 - } - ], - "title": "Spotlight", - "type": "radialbar" - }, - { - "datasource": { - "type": "grafana-testdata-datasource" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "max": 100, - "min": 0, - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": 0 - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 4, - "x": 16, - "y": 1 - }, "id": 8, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -450,19 +369,18 @@ "gridPos": { "h": 6, "w": 4, - "x": 0, - "y": 7 + "x": 16, + "y": 1 }, "id": 22, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": false, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -527,19 +445,18 @@ "gridPos": { "h": 6, "w": 4, - "x": 4, - "y": 7 + "x": 20, + "y": 1 }, "id": 23, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": false, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -579,7 +496,7 @@ "h": 1, "w": 24, "x": 0, - "y": 13 + "y": 7 }, "id": 17, "panels": [], @@ -616,20 +533,19 @@ }, "gridPos": { "h": 6, - "w": 5, + "w": 4, "x": 0, - "y": 14 + "y": 8 }, "id": 18, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.1, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -693,20 +609,19 @@ }, "gridPos": { "h": 6, - "w": 5, - "x": 5, - "y": 14 + "w": 4, + "x": 4, + "y": 8 }, "id": 19, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.32, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -770,20 +685,19 @@ }, "gridPos": { "h": 6, - "w": 5, - "x": 10, - "y": 14 + "w": 4, + "x": 8, + "y": 8 }, "id": 20, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.57, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -847,20 +761,19 @@ }, "gridPos": { "h": 6, - "w": 5, - "x": 15, - "y": 14 + "w": 4, + "x": 12, + "y": 8 }, "id": 21, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidthFactor": 0.8, "effects": { "barGlow": true, "centerGlow": true, - "gradient": false, - "rounded": true, - "spotlight": true + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -900,7 +813,7 @@ "h": 1, "w": 24, "x": 0, - "y": 20 + "y": 14 }, "id": 24, "panels": [], @@ -941,20 +854,19 @@ }, "gridPos": { "h": 6, - "w": 6, + "w": 4, "x": 0, - "y": 21 + "y": 15 }, "id": 25, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1018,20 +930,19 @@ }, "gridPos": { "h": 6, - "w": 6, - "x": 6, - "y": 21 + "w": 4, + "x": 4, + "y": 15 }, "id": 26, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1095,20 +1006,19 @@ }, "gridPos": { "h": 6, - "w": 5, - "x": 12, - "y": 21 + "w": 4, + "x": 8, + "y": 15 }, "id": 29, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -1171,21 +1081,20 @@ "overrides": [] }, "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 27 + "h": 6, + "w": 4, + "x": 12, + "y": 15 }, "id": 30, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1248,21 +1157,20 @@ "overrides": [] }, "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 27 + "h": 6, + "w": 4, + "x": 16, + "y": 15 }, "id": 28, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.72, "effects": { "barGlow": false, "centerGlow": false, - "gradient": false, - "rounded": false, - "spotlight": false + "gradient": false }, "orientation": "auto", "reduceOptions": { @@ -1298,7 +1206,7 @@ "h": 1, "w": 24, "x": 0, - "y": 34 + "y": 21 }, "id": 31, "panels": [], @@ -1345,18 +1253,17 @@ "h": 10, "w": 7, "x": 0, - "y": 35 + "y": 22 }, "id": 32, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -1426,18 +1333,17 @@ "h": 10, "w": 7, "x": 7, - "y": 35 + "y": 22 }, "id": 34, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -1507,18 +1413,17 @@ "h": 10, "w": 6, "x": 14, - "y": 35 + "y": 22 }, "id": 33, "maxDataPoints": 20, "options": { + "barShape": "flat", "barWidthFactor": 0.9, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -1554,7 +1459,7 @@ "h": 1, "w": 24, "x": 0, - "y": 45 + "y": 32 }, "id": 6, "panels": [], @@ -1595,20 +1500,20 @@ "h": 6, "w": 24, "x": 0, - "y": 46 + "y": 33 }, "id": 9, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, + "endpointMarker": "glow", "glow": "both", "orientation": "auto", "reduceOptions": { @@ -1621,8 +1526,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -1677,19 +1581,18 @@ "h": 6, "w": 24, "x": 0, - "y": 52 + "y": 39 }, "id": 11, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.4, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -1703,8 +1606,7 @@ "shape": "gauge", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": true, - "spotlight": true + "sparkline": true }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -1731,7 +1633,7 @@ "h": 1, "w": 24, "x": 0, - "y": 58 + "y": 45 }, "id": 12, "panels": [], @@ -1773,19 +1675,18 @@ "h": 7, "w": 4, "x": 0, - "y": 59 + "y": 46 }, "id": 13, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.49, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -1799,8 +1700,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -1856,19 +1756,18 @@ "h": 7, "w": 5, "x": 4, - "y": 59 + "y": 46 }, "id": 14, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.49, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -1882,8 +1781,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -1938,19 +1836,18 @@ "h": 7, "w": 5, "x": 9, - "y": 59 + "y": 46 }, "id": 15, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.84, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -1964,8 +1861,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -2020,19 +1916,18 @@ "h": 7, "w": 6, "x": 14, - "y": 59 + "y": 46 }, "id": 16, "maxDataPoints": 20, "options": { + "barShape": "rounded", "barWidth": 12, "barWidthFactor": 0.66, "effects": { "barGlow": true, "centerGlow": true, - "gradient": true, - "rounded": true, - "spotlight": true + "gradient": true }, "glow": "both", "orientation": "auto", @@ -2046,8 +1941,7 @@ "shape": "circle", "showThresholdLabels": false, "showThresholdMarkers": false, - "sparkline": false, - "spotlight": true + "sparkline": false }, "pluginVersion": "13.0.0-pre", "targets": [ @@ -2074,7 +1968,7 @@ "h": 1, "w": 24, "x": 0, - "y": 66 + "y": 53 }, "id": 35, "panels": [], @@ -2105,20 +1999,19 @@ "overrides": [] }, "gridPos": { - "h": 8, - "w": 6, + "h": 5, + "w": 12, "x": 0, - "y": 67 + "y": 54 }, "id": 36, "options": { + "barShape": "flat", "barWidthFactor": 0.5, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -2171,20 +2064,19 @@ "overrides": [] }, "gridPos": { - "h": 8, - "w": 6, - "x": 6, - "y": 67 + "h": 5, + "w": 12, + "x": 12, + "y": 54 }, "id": 37, "options": { + "barShape": "flat", "barWidthFactor": 0.5, "effects": { "barGlow": false, "centerGlow": false, - "gradient": true, - "rounded": false, - "spotlight": false + "gradient": true }, "orientation": "auto", "reduceOptions": { @@ -2224,5 +2116,6 @@ "timezone": "browser", "title": "Panel tests - Gauge (new)", "uid": "panel-tests-gauge-new", - "version": 9 + "version": 22, + "weekStart": "" } diff --git a/devenv/dev-dashboards/panel-gauge/gauge_tests_old_to_new.json b/devenv/dev-dashboards/panel-gauge/gauge_tests_old_to_new.json index b071ddff802..bee1ece914e 100644 --- a/devenv/dev-dashboards/panel-gauge/gauge_tests_old_to_new.json +++ b/devenv/dev-dashboards/panel-gauge/gauge_tests_old_to_new.json @@ -956,8 +956,6 @@ "effects": { "barGlow": false, "centerGlow": false, - "rounded": false, - "spotlight": false, "gradient": false } } diff --git a/packages/grafana-schema/src/raw/composable/newgauge/panelcfg/x/NewGaugePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/newgauge/panelcfg/x/NewGaugePanelCfg_types.gen.ts index aedc041cf71..7c29eb7b463 100644 --- a/packages/grafana-schema/src/raw/composable/newgauge/panelcfg/x/NewGaugePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/newgauge/panelcfg/x/NewGaugePanelCfg_types.gen.ts @@ -16,21 +16,19 @@ export interface GaugePanelEffects { barGlow?: boolean; centerGlow?: boolean; gradient?: boolean; - rounded?: boolean; - spotlight?: boolean; } export const defaultGaugePanelEffects: Partial = { barGlow: false, centerGlow: false, gradient: true, - rounded: false, - spotlight: false, }; export interface Options extends common.SingleStatBaseOptions { + barShape: ('flat' | 'rounded'); barWidthFactor: number; effects: GaugePanelEffects; + endpointMarker?: ('point' | 'glow' | 'none'); segmentCount: number; segmentSpacing: number; shape: ('circle' | 'gauge'); @@ -40,8 +38,10 @@ export interface Options extends common.SingleStatBaseOptions { } export const defaultOptions: Partial = { + barShape: 'flat', barWidthFactor: 0.5, effects: {}, + endpointMarker: 'point', segmentCount: 1, segmentSpacing: 0.3, shape: 'gauge', diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialArcPath.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialArcPath.tsx index f54f3cd6f22..f59614acd53 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialArcPath.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialArcPath.tsx @@ -1,52 +1,149 @@ -import { GaugeDimensions, toRad } from './utils'; +import { useId, memo, HTMLAttributes, ReactNode } from 'react'; -export interface RadialArcPathProps { - startAngle: number; - dimensions: GaugeDimensions; - color: string; - glowFilter?: string; +import { FieldDisplay } from '@grafana/data'; + +import { getBarEndcapColors, getGradientCss, getEndpointMarkerColors } from './colors'; +import { RadialShape, RadialGaugeDimensions, GradientStop } from './types'; +import { drawRadialArcPath, toRad } from './utils'; + +export interface RadialArcPathPropsBase { arcLengthDeg: number; + barEndcaps?: boolean; + dimensions: RadialGaugeDimensions; + fieldDisplay: FieldDisplay; roundedBars?: boolean; + shape: RadialShape; + endpointMarker?: 'point' | 'glow'; + startAngle: number; + glowFilter?: string; + endpointMarkerGlowFilter?: string; } -export function RadialArcPath({ - startAngle: angle, - dimensions, - color, - glowFilter, - arcLengthDeg, - roundedBars, -}: RadialArcPathProps) { - const { radius, centerX, centerY, barWidth } = dimensions; +interface RadialArcPathPropsWithColor extends RadialArcPathPropsBase { + color: string; +} - if (arcLengthDeg === 360) { - // For some reason a 100% full arc cannot be rendered - arcLengthDeg = 359.99; +interface RadialArcPathPropsWithGradient extends RadialArcPathPropsBase { + gradient: GradientStop[]; +} + +type RadialArcPathProps = RadialArcPathPropsWithColor | RadialArcPathPropsWithGradient; + +const ENDPOINT_MARKER_MIN_ANGLE = 10; +const DOT_OPACITY = 0.5; +const DOT_RADIUS_FACTOR = 0.4; +const MAX_DOT_RADIUS = 8; + +export const RadialArcPath = memo( + ({ + arcLengthDeg, + dimensions, + fieldDisplay, + roundedBars, + shape, + endpointMarker, + barEndcaps, + startAngle: angle, + glowFilter, + endpointMarkerGlowFilter, + ...rest + }: RadialArcPathProps) => { + const id = useId(); + + const bgDivStyle: HTMLAttributes['style'] = { width: '100%', height: '100%' }; + if ('color' in rest) { + bgDivStyle.backgroundColor = rest.color; + } else { + bgDivStyle.backgroundImage = getGradientCss(rest.gradient, shape); + } + + const { radius, centerX, centerY, barWidth } = dimensions; + + const path = drawRadialArcPath(angle, arcLengthDeg, dimensions, roundedBars); + + const startRadians = toRad(angle); + const endRadians = toRad(angle + arcLengthDeg); + + const xStart = centerX + radius * Math.cos(startRadians); + const yStart = centerY + radius * Math.sin(startRadians); + const xEnd = centerX + radius * Math.cos(endRadians); + const yEnd = centerY + radius * Math.sin(endRadians); + + const dotRadius = + endpointMarker === 'point' ? Math.min((barWidth / 2) * DOT_RADIUS_FACTOR, MAX_DOT_RADIUS) : barWidth / 2; + + let barEndcapColors: [string, string] | undefined; + let endpointMarks: ReactNode = null; + if ('gradient' in rest) { + if (endpointMarker && (rest.gradient?.length ?? 0) > 0) { + switch (endpointMarker) { + case 'point': + const [pointColorStart, pointColorEnd] = getEndpointMarkerColors( + rest.gradient!, + fieldDisplay.display.percent + ); + endpointMarks = ( + <> + {arcLengthDeg > ENDPOINT_MARKER_MIN_ANGLE && ( + + )} + + + ); + break; + case 'glow': + const offsetAngle = toRad(ENDPOINT_MARKER_MIN_ANGLE); + const xStartMark = centerX + radius * Math.cos(endRadians + offsetAngle); + const yStartMark = centerY + radius * Math.sin(endRadians + offsetAngle); + endpointMarks = + arcLengthDeg > ENDPOINT_MARKER_MIN_ANGLE ? ( + + ) : null; + break; + default: + break; + } + } + + if (barEndcaps) { + barEndcapColors = getBarEndcapColors(rest.gradient, fieldDisplay.display.percent); + } + } + + return ( + <> + {/* FIXME: optimize this by only using clippath + foreign obj for gradients */} + + + + + + +
+ + {barEndcapColors?.[0] && } + {barEndcapColors?.[1] && ( + + )} + + + {endpointMarks} + + ); } +); - const startRadians = toRad(angle); - const endRadians = toRad(angle + arcLengthDeg); - - let x1 = centerX + radius * Math.cos(startRadians); - let y1 = centerY + radius * Math.sin(startRadians); - let x2 = centerX + radius * Math.cos(endRadians); - let y2 = centerY + radius * Math.sin(endRadians); - - const largeArc = arcLengthDeg > 180 ? 1 : 0; - - const path = ['M', x1, y1, 'A', radius, radius, 0, largeArc, 1, x2, y2].join(' '); - - return ( - - ); -} +RadialArcPath.displayName = 'RadialArcPath'; diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialBar.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialBar.tsx index 2bacc3af3e8..719ec52c625 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialBar.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialBar.tsx @@ -1,97 +1,64 @@ -import { GrafanaTheme2 } from '@grafana/data'; +import { FALLBACK_COLOR, FieldDisplay } from '@grafana/data'; import { useTheme2 } from '../../themes/ThemeContext'; import { RadialArcPath } from './RadialArcPath'; -import { RadialColorDefs } from './RadialColorDefs'; -import { GaugeDimensions, toRad } from './utils'; +import { RadialShape, RadialGaugeDimensions, GradientStop } from './types'; export interface RadialBarProps { - dimensions: GaugeDimensions; - colorDefs: RadialColorDefs; - angleRange: number; angle: number; - startAngle: number; + angleRange: number; + dimensions: RadialGaugeDimensions; + fieldDisplay: FieldDisplay; + gradient?: GradientStop[]; roundedBars?: boolean; - spotlightStroke: string; + endpointMarker?: 'point' | 'glow'; + shape: RadialShape; + startAngle: number; glowFilter?: string; + endpointMarkerGlowFilter?: string; } export function RadialBar({ - dimensions, - colorDefs, - angleRange, angle, - startAngle, + angleRange, + dimensions, + fieldDisplay, + gradient, roundedBars, - spotlightStroke, + endpointMarker, + shape, + startAngle, glowFilter, + endpointMarkerGlowFilter, }: RadialBarProps) { const theme = useTheme2(); - + const colorProps = gradient ? { gradient } : { color: fieldDisplay.display.color ?? FALLBACK_COLOR }; return ( <> - - {/** Track */} - - {/** The colored bar */} - - {spotlightStroke && angle > 8 && ( - - )} - - {colorDefs.getDefs()} + {/** Track */} + + {/** The colored bar */} + ); } - -interface SpotlightEffectProps { - dimensions: GaugeDimensions; - angle: number; - glowFilter?: string; - spotlightStroke: string; - theme: GrafanaTheme2; - roundedBars?: boolean; -} - -function SpotlightSquareEffect({ dimensions, angle, glowFilter, spotlightStroke, roundedBars }: SpotlightEffectProps) { - const { radius, centerX, centerY, barWidth } = dimensions; - - const angleRadian = toRad(angle); - const x1 = centerX + radius * Math.cos(angleRadian - 0.2); - const y1 = centerY + radius * Math.sin(angleRadian - 0.2); - const x2 = centerX + radius * Math.cos(angleRadian); - const y2 = centerY + radius * Math.sin(angleRadian); - - const path = ['M', x1, y1, 'A', radius, radius, 0, 0, 1, x2, y2].join(' '); - - return ( - - ); -} diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialBarSegmented.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialBarSegmented.tsx index dbc93d2da7f..b51cb4ce2f1 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialBarSegmented.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialBarSegmented.tsx @@ -1,126 +1,74 @@ -import { FieldDisplay } from '@grafana/data'; +import { memo } from 'react'; + +import { FALLBACK_COLOR, FieldDisplay } from '@grafana/data'; import { useTheme2 } from '../../themes/ThemeContext'; import { RadialArcPath } from './RadialArcPath'; -import { RadialColorDefs } from './RadialColorDefs'; -import { GaugeDimensions } from './utils'; +import { RadialShape, RadialGaugeDimensions, GradientStop } from './types'; +import { + getAngleBetweenSegments, + getFieldConfigMinMax, + getFieldDisplayProcessor, + getOptimalSegmentCount, +} from './utils'; export interface RadialBarSegmentedProps { fieldDisplay: FieldDisplay; - dimensions: GaugeDimensions; - colorDefs: RadialColorDefs; + dimensions: RadialGaugeDimensions; angleRange: number; startAngle: number; glowFilter?: string; segmentCount: number; segmentSpacing: number; + shape: RadialShape; + gradient?: GradientStop[]; } -export function RadialBarSegmented({ - fieldDisplay, - dimensions, - startAngle, - angleRange, - glowFilter, - segmentCount, - segmentSpacing, - colorDefs, -}: RadialBarSegmentedProps) { - const segments: React.ReactNode[] = []; - const theme = useTheme2(); - const segmentCountAdjusted = getOptimalSegmentCount(dimensions, segmentSpacing, segmentCount, angleRange); - const min = fieldDisplay.field.min ?? 0; - const max = fieldDisplay.field.max ?? 100; - const value = fieldDisplay.display.numeric; - const angleBetweenSegments = getAngleBetweenSegments(segmentSpacing, segmentCount, angleRange); - const segmentArcLengthDeg = angleRange / segmentCountAdjusted - angleBetweenSegments; +export const RadialBarSegmented = memo( + ({ + fieldDisplay, + dimensions, + startAngle, + angleRange, + glowFilter, + gradient, + segmentCount, + segmentSpacing, + shape, + }: RadialBarSegmentedProps) => { + const theme = useTheme2(); + const segments: React.ReactNode[] = []; + const segmentCountAdjusted = getOptimalSegmentCount(dimensions, segmentSpacing, segmentCount, angleRange); + const [min, max] = getFieldConfigMinMax(fieldDisplay); + const value = fieldDisplay.display.numeric; + const angleBetweenSegments = getAngleBetweenSegments(segmentSpacing, segmentCount, angleRange); + const segmentArcLengthDeg = angleRange / segmentCountAdjusted - angleBetweenSegments; + const displayProcessor = getFieldDisplayProcessor(fieldDisplay); - for (let i = 0; i < segmentCountAdjusted; i++) { - const angleValue = min + ((max - min) / segmentCountAdjusted) * i; - const angleColor = colorDefs.getSegmentColor(angleValue); - const segmentAngle = startAngle + (angleRange / segmentCountAdjusted) * i + 0.01; - const segmentColor = angleValue >= value ? theme.colors.action.hover : angleColor; + for (let i = 0; i < segmentCountAdjusted; i++) { + const angleValue = min + ((max - min) / segmentCountAdjusted) * i; + const segmentAngle = startAngle + (angleRange / segmentCountAdjusted) * i + 0.01; + const segmentColor = + angleValue >= value ? theme.colors.border.medium : (displayProcessor(angleValue).color ?? FALLBACK_COLOR); + const colorProps = angleValue < value && gradient ? { gradient } : { color: segmentColor }; - segments.push( - - ); + segments.push( + + ); + } + + return {segments}; } +); - return ( - <> - {segments} - {colorDefs.getDefs()} - - ); -} - -export function getAngleBetweenSegments(segmentSpacing: number, segmentCount: number, range: number) { - // Max spacing is 8 degrees between segments - // Changing this constant could be considered a breaking change - const maxAngleBetweenSegments = Math.max(range / 1.5 / segmentCount, 2); - return segmentSpacing * maxAngleBetweenSegments; -} - -function getOptimalSegmentCount( - dimensions: GaugeDimensions, - segmentSpacing: number, - segmentCount: number, - range: number -) { - const angleBetweenSegments = getAngleBetweenSegments(segmentSpacing, segmentCount, range); - - const innerRadius = dimensions.radius - dimensions.barWidth / 2; - const circumference = Math.PI * innerRadius * 2 * (range / 360); - const maxSegments = Math.floor(circumference / (angleBetweenSegments + 3)); - - return Math.min(maxSegments, segmentCount); -} - -// export function RadialSegmentLine({ -// gaugeId, -// center, -// angle, -// size, -// color, -// barWidth, -// roundedBars, -// glow, -// margin, -// segmentWidth, -// }: RadialSegmentProps) { -// const arcSize = size - barWidth; -// const radius = arcSize / 2 - margin; - -// const angleRad = (Math.PI * (angle - 90)) / 180; -// const lineLength = radius - barWidth; - -// const x1 = center + radius * Math.cos(angleRad); -// const y1 = center + radius * Math.sin(angleRad); -// const x2 = center + lineLength * Math.cos(angleRad); -// const y2 = center + lineLength * Math.sin(angleRad); - -// return ( -// -// ); -// } +RadialBarSegmented.displayName = 'RadialBarSegmented'; diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialColorDefs.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialColorDefs.tsx deleted file mode 100644 index 6bcf4876880..00000000000 --- a/packages/grafana-ui/src/components/RadialGauge/RadialColorDefs.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import tinycolor from 'tinycolor2'; - -import { DisplayProcessor, FALLBACK_COLOR, FieldDisplay, getFieldColorMode, GrafanaTheme2 } from '@grafana/data'; - -import { RadialGradientMode, RadialShape } from './RadialGauge'; -import { GaugeDimensions } from './utils'; - -export interface RadialColorDefsOptions { - gradient: RadialGradientMode; - fieldDisplay: FieldDisplay; - theme: GrafanaTheme2; - dimensions: GaugeDimensions; - shape: RadialShape; - gaugeId: string; - displayProcessor: DisplayProcessor; -} - -export class RadialColorDefs { - private colorToIds: Record = {}; - private defs: React.ReactNode[] = []; - - constructor(private options: RadialColorDefsOptions) {} - - getSegmentColor(forValue: number): string { - const { displayProcessor } = this.options; - const baseColor = displayProcessor(forValue).color ?? FALLBACK_COLOR; - - return this.getColor(baseColor, true); - } - - getColor(baseColor: string, forSegment?: boolean): string { - const { gradient, dimensions, gaugeId, fieldDisplay, shape, theme } = this.options; - - const id = `value-color-${baseColor}-${gaugeId}`; - - if (this.colorToIds[id]) { - return this.colorToIds[id]; - } - - // If no gradient, just return the base color - if (gradient === 'none') { - this.colorToIds[id] = baseColor; - return baseColor; - } - - const returnColor = (this.colorToIds[id] = `url(#${id})`); - const colorModeId = fieldDisplay.field.color?.mode; - const colorMode = getFieldColorMode(colorModeId); - const valuePercent = fieldDisplay.display.percent ?? 0; - - // Handle continusous color modes first - // If it's a segment color we don't want to do continuous gradients - if (colorMode.isContinuous && colorMode.getColors && !forSegment) { - const colors = colorMode.getColors(theme); - const count = colors.length; - - this.defs.push( - - {colors.map((stopColor, i) => ( - - ))} - - ); - - return returnColor; - } - - // For value based colors we want to stay more true to the specific color - // So a radial gradient that adds a bit of light and shade works best - if (colorMode.isByValue) { - const color1 = tinycolor(baseColor).darken(5); - - this.defs.push( - - - - - - ); - - return returnColor; - } - - // For fixed / palette based color scales we can create a more fun - // hue and light based linear gradient that we rotate/move with the value - - const x2 = shape === 'circle' ? 0 : dimensions.centerX + dimensions.radius; - const y2 = shape === 'circle' ? dimensions.centerY + dimensions.radius : 0; - const color1 = tinycolor(baseColor).spin(-20).darken(5); - const color2 = tinycolor(baseColor).saturate(20).spin(20).brighten(10); - - // this makes it so the gradient is always brightest at the current value - const transform = - shape === 'circle' - ? `rotate(${360 * valuePercent - 180} ${dimensions.centerX} ${dimensions.centerY})` - : `translate(-${dimensions.radius * 2 * (1 - valuePercent)}, 0)`; - - this.defs.push( - - {theme.isDark ? ( - <> - - - - ) : ( - <> - - - - )} - - ); - - return returnColor; - } - - getMainBarColor(): string { - return this.getColor(this.options.fieldDisplay.display.color ?? FALLBACK_COLOR); - } - - getDefs(): React.ReactNode[] { - return this.defs; - } -} diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.story.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.story.tsx index c098beda886..b0574ef3f87 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.story.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.story.tsx @@ -13,7 +13,8 @@ import { FieldColorModeId } from '@grafana/schema'; import { useTheme2 } from '../../themes/ThemeContext'; import { Stack } from '../Layout/Stack/Stack'; -import { RadialGauge, RadialGaugeProps, RadialGradientMode, RadialShape, RadialTextMode } from './RadialGauge'; +import { RadialGauge, RadialGaugeProps } from './RadialGauge'; +import { RadialShape, RadialTextMode } from './types'; interface StoryProps extends RadialGaugeProps { value: number; @@ -31,10 +32,27 @@ const meta: Meta = { controls: { exclude: ['theme', 'values', 'vizCount'], }, + a11y: { + config: { + rules: [ + { + id: 'scrollable-region-focusable', + selector: 'body', + enabled: false, + }, + // NOTE: this is necessary due to a false positive with the filered svg glow in one of the examples. + // The color-contrast in this component should be accessible! + { + id: 'color-contrast', + selector: 'text', + enabled: false, + }, + ], + }, + }, }, args: { barWidthFactor: 0.2, - spotlight: false, glowBar: false, glowCenter: false, sparkline: false, @@ -42,7 +60,7 @@ const meta: Meta = { width: 200, height: 200, shape: 'circle', - gradient: 'none', + gradient: false, seriesCount: 1, segmentCount: 0, segmentSpacing: 0.2, @@ -56,14 +74,14 @@ const meta: Meta = { width: { control: { type: 'range', min: 50, max: 600 } }, height: { control: { type: 'range', min: 50, max: 600 } }, value: { control: { type: 'range', min: 0, max: 110 } }, - spotlight: { control: 'boolean' }, roundedBars: { control: 'boolean' }, sparkline: { control: 'boolean' }, thresholdsBar: { control: 'boolean' }, - gradient: { control: { type: 'radio' } }, + gradient: { control: { type: 'boolean' } }, seriesCount: { control: { type: 'range', min: 1, max: 20 } }, segmentCount: { control: { type: 'range', min: 0, max: 100 } }, segmentSpacing: { control: { type: 'range', min: 0, max: 1, step: 0.01 } }, + endpointMarker: { control: { type: 'select' }, options: ['none', 'point', 'glow'] }, colorScheme: { control: { type: 'select' }, options: [ @@ -102,57 +120,17 @@ export const Examples: StoryFn = (args) => {
Bar width
- - - - + + + +
Effects
- - - - + + + +
Shape: Gauge & color scale
@@ -160,14 +138,14 @@ export const Examples: StoryFn = (args) => { value={40} shape="gauge" width={250} - gradient="auto" + gradient colorScheme={FieldColorModeId.ContinuousGrYlRd} glowCenter={true} barWidthFactor={0.6} /> = (args) => { value={args.value ?? 70} color="blue" shape="gauge" - gradient="auto" + gradient sparkline={true} - spotlight glowBar={true} glowCenter={true} barWidthFactor={0.2} @@ -194,9 +171,8 @@ export const Examples: StoryFn = (args) => { value={args.value ?? 30} color="green" shape="gauge" - gradient="auto" + gradient sparkline={true} - spotlight glowBar={true} glowCenter={true} barWidthFactor={0.8} @@ -206,9 +182,8 @@ export const Examples: StoryFn = (args) => { color="red" shape="gauge" width={250} - gradient="auto" + gradient sparkline={true} - spotlight glowBar={true} glowCenter={true} barWidthFactor={0.2} @@ -218,9 +193,8 @@ export const Examples: StoryFn = (args) => { color="red" width={250} shape="gauge" - gradient="auto" + gradient sparkline={true} - spotlight glowBar={true} glowCenter={true} barWidthFactor={0.8} @@ -231,7 +205,7 @@ export const Examples: StoryFn = (args) => { = (args) => { = (args) => { = (args) => { = (args) => { value={args.value ?? 80} width={250} colorScheme={FieldColorModeId.ContinuousGrYlRd} - spotlight shape="gauge" - gradient="auto" + gradient glowBar={true} glowCenter={true} segmentCount={40} @@ -285,10 +257,9 @@ export const Examples: StoryFn = (args) => { @@ -296,7 +267,7 @@ export const Examples: StoryFn = (args) => { value={args.value ?? 70} width={250} colorScheme={FieldColorModeId.Thresholds} - gradient="auto" + gradient glowCenter={true} thresholdsBar={true} roundedBars={false} @@ -307,7 +278,7 @@ export const Examples: StoryFn = (args) => { value={args.value ?? 70} width={250} colorScheme={FieldColorModeId.Thresholds} - gradient="auto" + gradient glowCenter={true} thresholdsBar={true} roundedBars={false} @@ -347,14 +318,12 @@ export const Temp: StoryFn = (args) => { shape="gauge" roundedBars={false} barWidthFactor={0.8} - spotlight /> ); }; interface ExampleProps { - gradient?: RadialGradientMode; color?: string; seriesName?: string; value?: number; @@ -363,7 +332,7 @@ interface ExampleProps { max?: number; width?: number; height?: number; - spotlight?: boolean; + gradient?: boolean; glowBar?: boolean; glowCenter?: boolean; barWidthFactor?: number; @@ -376,12 +345,12 @@ interface ExampleProps { roundedBars?: boolean; thresholdsBar?: boolean; colorScheme?: FieldColorModeId; + endpointMarker?: RadialGaugeProps['endpointMarker']; decimals?: number; showScaleLabels?: boolean; } export function RadialGaugeExample({ - gradient = 'none', color, seriesName = 'Server A', value = 70, @@ -390,7 +359,7 @@ export function RadialGaugeExample({ max = 100, width = 200, height = 200, - spotlight = false, + gradient = false, glowBar = false, glowCenter = false, barWidthFactor = 0.4, @@ -403,6 +372,7 @@ export function RadialGaugeExample({ roundedBars = false, thresholdsBar = false, colorScheme = FieldColorModeId.Thresholds, + endpointMarker = 'glow', decimals = 0, showScaleLabels, }: ExampleProps) { @@ -480,7 +450,6 @@ export function RadialGaugeExample({ barWidthFactor={barWidthFactor} gradient={gradient} shape={shape} - spotlight={spotlight} glowBar={glowBar} glowCenter={glowCenter} textMode={textMode} @@ -490,6 +459,7 @@ export function RadialGaugeExample({ roundedBars={roundedBars} thresholdsBar={thresholdsBar} showScaleLabels={showScaleLabels} + endpointMarker={endpointMarker} /> ); } diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.test.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.test.tsx index e2fed36ec3f..783e3b764da 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.test.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.test.tsx @@ -1,13 +1,28 @@ import { render, screen } from '@testing-library/react'; +import { ComponentProps } from 'react'; import { RadialGaugeExample } from './RadialGauge.story'; describe('RadialGauge', () => { - it('should render', () => { - render(); - - expect(screen.getByRole('img')).toBeInTheDocument(); - }); + it.each([ + { description: 'default', props: {} }, + { description: 'gauge shape', props: { shape: 'gauge' } }, + { description: 'with gradient', props: { gradient: true } }, + { description: 'with glow bar', props: { glowBar: true } }, + { description: 'with glow center', props: { glowCenter: true } }, + { description: 'with segments', props: { segmentCount: 5 } }, + { description: 'with rounded bars', props: { roundedBars: true } }, + { description: 'with endpoint marker glow', props: { roundedBars: true, endpointMarker: 'glow' } }, + { description: 'with endpoint marker point', props: { roundedBars: true, endpointMarker: 'point' } }, + { description: 'with thresholds bar', props: { thresholdsBar: true } }, + { description: 'with sparkline', props: { sparkline: true } }, + ] satisfies Array<{ description: string; props?: ComponentProps }>)( + 'should render $description without throwing', + ({ props }) => { + render(); + expect(screen.getByRole('img')).toBeInTheDocument(); + } + ); it('should render threshold labels', () => { render(); diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx index fadabf8ec72..1251a364230 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx @@ -1,14 +1,7 @@ import { css, cx } from '@emotion/css'; -import { isNumber } from 'lodash'; import { useId } from 'react'; -import { - DisplayValueAlignmentFactors, - FieldDisplay, - getDisplayProcessor, - GrafanaTheme2, - TimeRange, -} from '@grafana/data'; +import { DisplayValueAlignmentFactors, FALLBACK_COLOR, FieldDisplay, GrafanaTheme2, TimeRange } from '@grafana/data'; import { t } from '@grafana/i18n'; import { useStyles2, useTheme2 } from '../../themes/ThemeContext'; @@ -16,12 +9,13 @@ import { getFormattedThresholds } from '../Gauge/utils'; import { RadialBar } from './RadialBar'; import { RadialBarSegmented } from './RadialBarSegmented'; -import { RadialColorDefs } from './RadialColorDefs'; import { RadialScaleLabels } from './RadialScaleLabels'; import { RadialSparkline } from './RadialSparkline'; import { RadialText } from './RadialText'; import { ThresholdsBar } from './ThresholdsBar'; +import { buildGradientColors } from './colors'; import { GlowGradient, MiddleCircleGlow, SpotlightGradient } from './effects'; +import { RadialShape, RadialTextMode } from './types'; import { calculateDimensions, getValueAngleForValue } from './utils'; export interface RadialGaugeProps { @@ -32,7 +26,7 @@ export interface RadialGaugeProps { * Circle or gauge (partial circle) */ shape?: RadialShape; - gradient?: RadialGradientMode; + gradient?: boolean; /** * Bar width is always relative to size of the gauge. * But this gives you control over the width relative to size. @@ -40,12 +34,14 @@ export interface RadialGaugeProps { * Defaults to 0.4 **/ barWidthFactor?: number; - /** Adds a white spotlight for the end position */ - spotlight?: boolean; glowBar?: boolean; glowCenter?: boolean; roundedBars?: boolean; thresholdsBar?: boolean; + /** + * Specify if an endpoint marker should be shown at the end of the bar + */ + endpointMarker?: 'point' | 'glow'; /** * Number of segments depends on size of gauge but this * factor 1-10 gives you relative control @@ -75,10 +71,6 @@ export interface RadialGaugeProps { timeRange?: TimeRange; } -export type RadialGradientMode = 'none' | 'auto'; -export type RadialTextMode = 'auto' | 'value_and_name' | 'value' | 'name' | 'none'; -export type RadialShape = 'circle' | 'gauge'; - /** * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-radialgauge--docs */ @@ -87,9 +79,8 @@ export function RadialGauge(props: RadialGaugeProps) { width = 256, height = 256, shape = 'circle', - gradient = 'none', + gradient = false, barWidthFactor = 0.4, - spotlight = false, glowBar = false, glowCenter = false, textMode = 'auto', @@ -99,6 +90,7 @@ export function RadialGauge(props: RadialGaugeProps) { roundedBars = true, thresholdsBar = false, showScaleLabels = false, + endpointMarker, onClick, values, } = props; @@ -121,7 +113,8 @@ export function RadialGauge(props: RadialGaugeProps) { for (let barIndex = 0; barIndex < values.length; barIndex++) { const displayValue = values[barIndex]; const { angle, angleRange } = getValueAngleForValue(displayValue, startAngle, endAngle); - const color = displayValue.display.color ?? 'gray'; + const gradientStops = buildGradientColors(gradient, theme, displayValue); + const color = displayValue.display.color ?? FALLBACK_COLOR; const dimensions = calculateDimensions( width, height, @@ -134,20 +127,12 @@ export function RadialGauge(props: RadialGaugeProps) { showScaleLabels ); - const displayProcessor = getFieldDisplayProcessor(displayValue); + // FIXME: I want to move the ids for these filters into a context which the children + // can reference via a hook, rather than passing them down as props const spotlightGradientId = `spotlight-${barIndex}-${gaugeId}`; const glowFilterId = `glow-${gaugeId}`; - const colorDefs = new RadialColorDefs({ - gradient, - fieldDisplay: displayValue, - theme, - dimensions, - shape, - gaugeId, - displayProcessor, - }); - if (spotlight && theme.isDark) { + if (endpointMarker === 'glow') { defs.push( ); } else { @@ -179,13 +165,16 @@ export function RadialGauge(props: RadialGaugeProps) { ); } @@ -245,7 +234,8 @@ export function RadialGauge(props: RadialGaugeProps) { angleRange={angleRange} roundedBars={roundedBars} glowFilter={`url(#${glowFilterId})`} - colorDefs={colorDefs} + shape={shape} + gradient={gradientStops} /> ); } @@ -291,17 +281,6 @@ export function RadialGauge(props: RadialGaugeProps) { ); } -function getFieldDisplayProcessor(displayValue: FieldDisplay) { - if (displayValue.view && isNumber(displayValue.colIndex)) { - const dp = displayValue.view.getFieldDisplayProcessor(displayValue.colIndex); - if (dp) { - return dp; - } - } - - return getDisplayProcessor(); -} - function getStyles(theme: GrafanaTheme2) { return { vizWrapper: css({ diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialScaleLabels.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialScaleLabels.tsx index 994b3b35eac..6588150602a 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialScaleLabels.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialScaleLabels.tsx @@ -1,87 +1,84 @@ +import { memo } from 'react'; + import { FieldDisplay, GrafanaTheme2, Threshold } from '@grafana/data'; import { t } from '@grafana/i18n'; import { measureText } from '../../utils/measureText'; -import { GaugeDimensions, toCartesian } from './utils'; +import { RadialGaugeDimensions } from './types'; +import { getFieldConfigMinMax, toCartesian } from './utils'; interface RadialScaleLabelsProps { fieldDisplay: FieldDisplay; theme: GrafanaTheme2; thresholds: Threshold[]; - dimensions: GaugeDimensions; + dimensions: RadialGaugeDimensions; startAngle: number; endAngle: number; angleRange: number; } -export function RadialScaleLabels({ - fieldDisplay, - thresholds, - theme, - dimensions, - startAngle, - endAngle, - angleRange, -}: RadialScaleLabelsProps) { - const { centerX, centerY, scaleLabelsFontSize, scaleLabelsRadius } = dimensions; +const LINE_HEIGHT_FACTOR = 1.2; - const fieldConfig = fieldDisplay.field; - const min = fieldConfig.min ?? 0; - const max = fieldConfig.max ?? 100; +export const RadialScaleLabels = memo( + ({ fieldDisplay, thresholds, theme, dimensions, startAngle, endAngle, angleRange }: RadialScaleLabelsProps) => { + const { centerX, centerY, scaleLabelsFontSize, scaleLabelsRadius } = dimensions; + const [min, max] = getFieldConfigMinMax(fieldDisplay); - const fontSize = scaleLabelsFontSize; - const textLineHeight = scaleLabelsFontSize * 1.2; - const radius = scaleLabelsRadius - textLineHeight; + const fontSize = scaleLabelsFontSize; + const textLineHeight = scaleLabelsFontSize * LINE_HEIGHT_FACTOR; + const radius = scaleLabelsRadius - textLineHeight; - function getTextPosition(text: string, value: number, index: number) { - const isLast = index === thresholds.length - 1; - const isFirst = index === 0; + function getTextPosition(text: string, value: number, index: number) { + const isLast = index === thresholds.length - 1; + const isFirst = index === 0; - let valueDeg = ((value - min) / (max - min)) * angleRange; - let finalAngle = startAngle + valueDeg; + let valueDeg = ((value - min) / (max - min)) * angleRange; + let finalAngle = startAngle + valueDeg; - // Now adjust the final angle based on the label text width and the labels position on the arc - let measure = measureText(text, fontSize, theme.typography.fontWeightMedium); - let textWidthAngle = (measure.width / (2 * Math.PI * radius)) * angleRange; + // Now adjust the final angle based on the label text width and the labels position on the arc + let measure = measureText(text, fontSize, theme.typography.fontWeightMedium); + let textWidthAngle = (measure.width / (2 * Math.PI * radius)) * angleRange; - // the centering is different for gauge or circle shapes for some reason - finalAngle -= endAngle < 180 ? textWidthAngle : textWidthAngle / 2; + // the centering is different for gauge or circle shapes for some reason + finalAngle -= endAngle < 180 ? textWidthAngle : textWidthAngle / 2; - // For circle gauges we need to shift the first label more - if (isFirst) { - finalAngle += textWidthAngle; + // For circle gauges we need to shift the first label more + if (isFirst) { + finalAngle += textWidthAngle; + } + + // For circle gauges we need to shift the last label more + if (isLast && endAngle === 360) { + finalAngle -= textWidthAngle; + } + + const position = toCartesian(centerX, centerY, radius, finalAngle); + + return { ...position, transform: `rotate(${finalAngle}, ${position.x}, ${position.y})` }; } - // For circle gauges we need to shift the last label more - if (isLast && endAngle === 360) { - finalAngle -= textWidthAngle; - } - - const position = toCartesian(centerX, centerY, radius, finalAngle); - - return { ...position, transform: `rotate(${finalAngle}, ${position.x}, ${position.y})` }; + return ( + + {thresholds.map((threshold, index) => { + const labelPos = getTextPosition(String(threshold.value), threshold.value, index); + return ( + + {threshold.value} + + ); + })} + + ); } +); - return ( - - {thresholds.map((threshold, index) => { - const labelPos = getTextPosition(String(threshold.value), threshold.value, index); - - return ( - - {threshold.value} - - ); - })} - - ); -} +RadialScaleLabels.displayName = 'RadialScaleLabels'; diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialSparkline.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialSparkline.tsx index acb255a3f3e..2d6c45a14bf 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialSparkline.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialSparkline.tsx @@ -1,49 +1,76 @@ +import { memo, useMemo } from 'react'; + import { FieldDisplay, GrafanaTheme2, FieldConfig } from '@grafana/data'; import { GraphFieldConfig, GraphGradientMode, LineInterpolation } from '@grafana/schema'; import { Sparkline } from '../Sparkline/Sparkline'; -import { RadialShape, RadialTextMode } from './RadialGauge'; -import { GaugeDimensions } from './utils'; +import { RadialShape, RadialTextMode, RadialGaugeDimensions } from './types'; interface RadialSparklineProps { - sparkline: FieldDisplay['sparkline']; - dimensions: GaugeDimensions; - theme: GrafanaTheme2; color?: string; - shape?: RadialShape; + dimensions: RadialGaugeDimensions; + shape: RadialShape; + sparkline: FieldDisplay['sparkline']; textMode: Exclude; + theme: GrafanaTheme2; } -export function RadialSparkline({ sparkline, dimensions, theme, color, shape, textMode }: RadialSparklineProps) { - const { radius, barWidth } = dimensions; - if (!sparkline) { - return null; +const SPARKLINE_HEIGHT_DIVISOR = 4; +const SPARKLINE_HEIGHT_DIVISOR_NAME_AND_VALUE = 4; +const SPARKLINE_WIDTH_FACTOR_ARC = 1.4; +const SPARKLINE_WIDTH_FACTOR_CIRCLE = 1.6; +const SPARKLINE_TOP_OFFSET_DIVISOR_CIRCLE = 4; +const SPARKLINE_TOP_OFFSET_DIVISOR_CIRCLE_NAME_AND_VALUE = 3.3; +const SPARKLINE_SPACING = 8; + +export function getSparklineDimensions( + radius: number, + barWidth: number, + showNameAndValue: boolean, + shape: RadialShape +): { width: number; height: number } { + const height = radius / (showNameAndValue ? SPARKLINE_HEIGHT_DIVISOR_NAME_AND_VALUE : SPARKLINE_HEIGHT_DIVISOR); + const width = radius * (shape === 'gauge' ? SPARKLINE_WIDTH_FACTOR_ARC : SPARKLINE_WIDTH_FACTOR_CIRCLE) - barWidth; + return { width, height }; +} + +export const RadialSparkline = memo( + ({ sparkline, dimensions, theme, color, shape, textMode }: RadialSparklineProps) => { + const { radius, barWidth } = dimensions; + + const showNameAndValue = textMode === 'value_and_name'; + const { width, height } = getSparklineDimensions(radius, barWidth, showNameAndValue, shape); + const topPos = + shape === 'gauge' + ? dimensions.gaugeBottomY - height - SPARKLINE_SPACING + : `calc(50% + ${radius / (showNameAndValue ? SPARKLINE_TOP_OFFSET_DIVISOR_CIRCLE_NAME_AND_VALUE : SPARKLINE_TOP_OFFSET_DIVISOR_CIRCLE)}px)`; + + const config: FieldConfig = useMemo( + () => ({ + color: { + mode: 'fixed', + fixedColor: color ?? 'blue', + }, + custom: { + gradientMode: GraphGradientMode.Opacity, + fillOpacity: 40, + lineInterpolation: LineInterpolation.Smooth, + }, + }), + [color] + ); + + if (!sparkline) { + return null; + } + + return ( +
+ +
+ ); } +); - const showNameAndValue = textMode === 'value_and_name'; - const height = radius / (showNameAndValue ? 4 : 3); - const width = radius * (shape === 'gauge' ? 1.6 : 1.4) - barWidth; - const topPos = - shape === 'gauge' - ? `${dimensions.gaugeBottomY - height}px` - : `calc(50% + ${radius / (showNameAndValue ? 3.3 : 4)}px)`; - - const config: FieldConfig = { - color: { - mode: 'fixed', - fixedColor: color ?? 'blue', - }, - custom: { - gradientMode: GraphGradientMode.Opacity, - fillOpacity: 40, - lineInterpolation: LineInterpolation.Smooth, - }, - }; - - return ( -
- -
- ); -} +RadialSparkline.displayName = 'RadialSparkline'; diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialText.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialText.tsx index 51a1c64c842..69ab16e450e 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialText.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialText.tsx @@ -1,4 +1,5 @@ import { css } from '@emotion/css'; +import { memo } from 'react'; import { DisplayValue, @@ -11,13 +12,12 @@ import { import { useStyles2 } from '../../themes/ThemeContext'; import { calculateFontSize } from '../../utils/measureText'; -import { RadialShape, RadialTextMode } from './RadialGauge'; -import { GaugeDimensions } from './utils'; +import { RadialShape, RadialTextMode, RadialGaugeDimensions } from './types'; interface RadialTextProps { displayValue: DisplayValue; theme: GrafanaTheme2; - dimensions: GaugeDimensions; + dimensions: RadialGaugeDimensions; textMode: Exclude; shape: RadialShape; sparkline?: FieldSparkline; @@ -26,123 +26,137 @@ interface RadialTextProps { nameManualFontSize?: number; } -export function RadialText({ - displayValue, - theme, - dimensions, - textMode, - shape, - sparkline, - alignmentFactors, - valueManualFontSize, - nameManualFontSize, -}: RadialTextProps) { - const styles = useStyles2(getStyles); - const { centerX, centerY, radius, barWidth } = dimensions; +const LINE_HEIGHT_FACTOR = 1.21; +const VALUE_WIDTH_TO_RADIUS_FACTOR = 0.82; +const NAME_TO_HEIGHT_FACTOR = 0.45; +const LARGE_RADIUS_SCALING_DECAY = 0.86; +const MAX_TEXT_WIDTH_DIVISOR = 7; +const MAX_NAME_HEIGHT_DIVISOR = 4; +const VALUE_SPACE_PERCENTAGE = 0.7; +const SPARKLINE_SPACING = 8; +const MIN_VALUE_FONT_SIZE = 1; +const MIN_NAME_FONT_SIZE = 10; +const MIN_UNIT_FONT_SIZE = 6; - if (textMode === 'none') { - return null; - } +export const RadialText = memo( + ({ + displayValue, + theme, + dimensions, + textMode, + shape, + sparkline, + alignmentFactors, + valueManualFontSize, + nameManualFontSize, + }: RadialTextProps) => { + const styles = useStyles2(getStyles); + const { centerX, centerY, radius, barWidth } = dimensions; - const nameToAlignTo = (alignmentFactors ? alignmentFactors.title : displayValue.title) ?? ''; - const valueToAlignTo = formattedValueToString(alignmentFactors ? alignmentFactors : displayValue); + if (textMode === 'none') { + return null; + } - const showValue = textMode === 'value' || textMode === 'value_and_name'; - const showName = textMode === 'name' || textMode === 'value_and_name'; - const maxTextWidth = radius * 2 - barWidth - radius / 7; + const nameToAlignTo = (alignmentFactors ? alignmentFactors.title : displayValue.title) ?? ''; + const valueToAlignTo = formattedValueToString(alignmentFactors ? alignmentFactors : displayValue); - // Not sure where this comes from but svg text is not using body line-height - const lineHeight = 1.21; - const valueWidthToRadiusFactor = 0.82; - const nameToHeightFactor = 0.45; - const largeRadiusScalingDecay = 0.86; + const showValue = textMode === 'value' || textMode === 'value_and_name'; + const showName = textMode === 'name' || textMode === 'value_and_name'; + const maxTextWidth = radius * 2 - barWidth - radius / MAX_TEXT_WIDTH_DIVISOR; - // This pow 0.92 factor is to create a decay so the font size does not become rediculously large for very large panels - let maxValueHeight = valueWidthToRadiusFactor * Math.pow(radius, largeRadiusScalingDecay); - let maxNameHeight = radius / 4; + // This pow 0.92 factor is to create a decay so the font size does not become rediculously large for very large panels + let maxValueHeight = VALUE_WIDTH_TO_RADIUS_FACTOR * Math.pow(radius, LARGE_RADIUS_SCALING_DECAY); + let maxNameHeight = radius / MAX_NAME_HEIGHT_DIVISOR; - if (showValue && showName) { - maxValueHeight = valueWidthToRadiusFactor * Math.pow(radius, largeRadiusScalingDecay); - maxNameHeight = nameToHeightFactor * Math.pow(radius, largeRadiusScalingDecay); - } + if (showValue && showName) { + maxValueHeight = VALUE_WIDTH_TO_RADIUS_FACTOR * Math.pow(radius, LARGE_RADIUS_SCALING_DECAY); + maxNameHeight = NAME_TO_HEIGHT_FACTOR * Math.pow(radius, LARGE_RADIUS_SCALING_DECAY); + } - const valueFontSize = - valueManualFontSize ?? - calculateFontSize( - valueToAlignTo, - maxTextWidth, - maxValueHeight, - lineHeight, - undefined, - theme.typography.body.fontWeight + const valueFontSize = Math.max( + valueManualFontSize ?? + calculateFontSize( + valueToAlignTo, + maxTextWidth, + maxValueHeight, + LINE_HEIGHT_FACTOR, + undefined, + theme.typography.body.fontWeight + ), + MIN_VALUE_FONT_SIZE ); - const nameFontSize = - nameManualFontSize ?? - calculateFontSize( - nameToAlignTo, - maxTextWidth, - maxNameHeight, - lineHeight, - undefined, - theme.typography.body.fontWeight + const nameFontSize = Math.max( + nameManualFontSize ?? + calculateFontSize( + nameToAlignTo, + maxTextWidth, + maxNameHeight, + LINE_HEIGHT_FACTOR, + undefined, + theme.typography.body.fontWeight + ), + MIN_NAME_FONT_SIZE ); - const unitFontSize = Math.max(valueFontSize * 0.7, 5); - const valueHeight = valueFontSize * lineHeight; - const nameHeight = nameFontSize * lineHeight; + const unitFontSize = Math.max(valueFontSize * VALUE_SPACE_PERCENTAGE, MIN_UNIT_FONT_SIZE); + const valueHeight = valueFontSize * LINE_HEIGHT_FACTOR; + const nameHeight = nameFontSize * LINE_HEIGHT_FACTOR; - const valueY = showName ? centerY - nameHeight * 0.3 : centerY; - const nameY = showValue ? valueY + valueHeight * 0.7 : centerY; - const nameColor = showValue ? theme.colors.text.secondary : theme.colors.text.primary; - const suffixShift = (valueFontSize - unitFontSize * 1.2) / 2; + const valueY = showName ? centerY - nameHeight * (1 - VALUE_SPACE_PERCENTAGE) : centerY; + const nameY = showValue ? valueY + valueHeight * VALUE_SPACE_PERCENTAGE : centerY; + const nameColor = showValue ? theme.colors.text.secondary : theme.colors.text.primary; + const suffixShift = (valueFontSize - unitFontSize * LINE_HEIGHT_FACTOR) / 2; - // adjust the text up on gauges and when sparklines are present - let yOffset = 0; - if (shape === 'gauge') { - // we render from the center of the gauge, so move up by half of half of the total height - yOffset -= (valueHeight + nameHeight) / 4; - } - if (sparkline) { - yOffset -= 8; + // adjust the text up on gauges and when sparklines are present + let yOffset = 0; + if (shape === 'gauge') { + // we render from the center of the gauge, so move up by half of half of the total height + yOffset -= (valueHeight + nameHeight) / 4; + } + if (sparkline) { + yOffset -= SPARKLINE_SPACING; + } + + return ( + + {showValue && ( + + {displayValue.prefix ?? ''} + {displayValue.text} + + {displayValue.suffix ?? ''} + + + )} + {showName && ( + + {displayValue.title} + + )} + + ); } +); - return ( - - {showValue && ( - - {displayValue.prefix ?? ''} - {displayValue.text} - - {displayValue.suffix ?? ''} - - - )} - {showName && ( - - {displayValue.title} - - )} - - ); -} +RadialText.displayName = 'RadialText'; -const getStyles = (theme: GrafanaTheme2) => ({ +const getStyles = (_theme: GrafanaTheme2) => ({ text: css({ verticalAlign: 'bottom', }), diff --git a/packages/grafana-ui/src/components/RadialGauge/ThresholdsBar.tsx b/packages/grafana-ui/src/components/RadialGauge/ThresholdsBar.tsx index 602038ccf10..cb2829934b9 100644 --- a/packages/grafana-ui/src/components/RadialGauge/ThresholdsBar.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/ThresholdsBar.tsx @@ -1,20 +1,22 @@ import { FieldDisplay, Threshold } from '@grafana/data'; import { RadialArcPath } from './RadialArcPath'; -import { RadialColorDefs } from './RadialColorDefs'; -import { GaugeDimensions } from './utils'; +import { GradientStop, RadialGaugeDimensions, RadialShape } from './types'; +import { getFieldConfigMinMax } from './utils'; -export interface Props { - dimensions: GaugeDimensions; +interface ThresholdsBarProps { + dimensions: RadialGaugeDimensions; angleRange: number; startAngle: number; endAngle: number; + shape: RadialShape; fieldDisplay: FieldDisplay; roundedBars?: boolean; glowFilter?: string; - colorDefs: RadialColorDefs; thresholds: Threshold[]; + gradient?: GradientStop[]; } + export function ThresholdsBar({ dimensions, fieldDisplay, @@ -22,19 +24,18 @@ export function ThresholdsBar({ angleRange, roundedBars, glowFilter, - colorDefs, thresholds, -}: Props) { - const fieldConfig = fieldDisplay.field; - const min = fieldConfig.min ?? 0; - const max = fieldConfig.max ?? 100; - + shape, + gradient, +}: ThresholdsBarProps) { const thresholdDimensions = { ...dimensions, barWidth: dimensions.thresholdsBarWidth, radius: dimensions.thresholdsBarRadius, }; + const [min, max] = getFieldConfigMinMax(fieldDisplay); + let currentStart = startAngle; let paths: React.ReactNode[] = []; @@ -48,27 +49,26 @@ export function ThresholdsBar({ valueDeg = 0; } - let lengthDeg = valueDeg - currentStart + startAngle; + const lengthDeg = valueDeg - currentStart + startAngle; + const colorProps = gradient ? { gradient } : { color: threshold.color }; paths.push( ); currentStart += lengthDeg; } - return ( - <> - {paths} - {colorDefs.getDefs()} - - ); + return {paths}; } diff --git a/packages/grafana-ui/src/components/RadialGauge/__snapshots__/colors.test.ts.snap b/packages/grafana-ui/src/components/RadialGauge/__snapshots__/colors.test.ts.snap new file mode 100644 index 00000000000..97b053c2d61 --- /dev/null +++ b/packages/grafana-ui/src/components/RadialGauge/__snapshots__/colors.test.ts.snap @@ -0,0 +1,144 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RadialGauge color utils buildGradientColors should map threshold colors correctly (with baseColor if displayProcessor does not return colors) 1`] = ` +[ + { + "color": "#444444", + "percent": 0, + }, + { + "color": "#FADE2A", + "percent": 0.5, + }, + { + "color": "#F2495C", + "percent": 0.8, + }, + { + "color": "#444444", + "percent": 1, + }, +] +`; + +exports[`RadialGauge color utils buildGradientColors should map threshold colors correctly (with baseColor if displayProcessor does not return colors) 2`] = ` +[ + { + "color": "#FF0000", + "percent": 0, + }, + { + "color": "#FADE2A", + "percent": 0.5, + }, + { + "color": "#F2495C", + "percent": 0.8, + }, + { + "color": "#FF0000", + "percent": 1, + }, +] +`; + +exports[`RadialGauge color utils buildGradientColors should return gradient colors for by-value color mode in dark theme 1`] = ` +[ + { + "color": "#181b1f", + "percent": 0, + }, + { + "color": "#1F60C4", + "percent": 1, + }, +] +`; + +exports[`RadialGauge color utils buildGradientColors should return gradient colors for by-value color mode in light theme 1`] = ` +[ + { + "color": "#ffffff", + "percent": 0, + }, + { + "color": "#1250B0", + "percent": 1, + }, +] +`; + +exports[`RadialGauge color utils buildGradientColors should return gradient colors for continuous color modes 1`] = ` +[ + { + "color": "rgb(0, 32, 81)", + "percent": 0, + }, + { + "color": "rgb(17, 54, 108)", + "percent": 0.125, + }, + { + "color": "rgb(60, 77, 110)", + "percent": 0.25, + }, + { + "color": "rgb(98, 100, 111)", + "percent": 0.375, + }, + { + "color": "rgb(127, 124, 117)", + "percent": 0.5, + }, + { + "color": "rgb(154, 148, 120)", + "percent": 0.625, + }, + { + "color": "rgb(187, 175, 113)", + "percent": 0.75, + }, + { + "color": "rgb(226, 203, 92)", + "percent": 0.875, + }, + { + "color": "rgb(253, 234, 69)", + "percent": 1, + }, +] +`; + +exports[`RadialGauge color utils buildGradientColors should return gradient colors for fixed color mode in dark theme 1`] = ` +[ + { + "color": "#37237a", + "percent": 0, + }, + { + "color": "#a146da", + "percent": 0.75, + }, + { + "color": "#a146da", + "percent": 1, + }, +] +`; + +exports[`RadialGauge color utils buildGradientColors should return gradient colors for fixed color mode in light theme 1`] = ` +[ + { + "color": "#a146da", + "percent": 0, + }, + { + "color": "#3e2b9a", + "percent": 0.75, + }, + { + "color": "#3e2b9a", + "percent": 1, + }, +] +`; diff --git a/packages/grafana-ui/src/components/RadialGauge/__snapshots__/utils.test.ts.snap b/packages/grafana-ui/src/components/RadialGauge/__snapshots__/utils.test.ts.snap new file mode 100644 index 00000000000..db4c1c40882 --- /dev/null +++ b/packages/grafana-ui/src/components/RadialGauge/__snapshots__/utils.test.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RadialGauge utils drawRadialArcPath should draw correct path for center x and y 1`] = `"M 150 110 A 90 90 0 1 1 149.98429203681178 110.00000137077838 A 10 10 0 0 1 149.98778269529805 130.00000106616096 A 70 70 0 1 0 150 130 A 10 10 0 0 1 150 110 Z"`; + +exports[`RadialGauge utils drawRadialArcPath should draw correct path for half arc 1`] = `"M 100 10 A 90 90 0 0 1 100 190 L 100 170 A 70 70 0 0 0 100 30 L 100 10 Z"`; + +exports[`RadialGauge utils drawRadialArcPath should draw correct path for narrow bar width 1`] = `"M 100 17.5 A 82.5 82.5 0 0 1 100 182.5 L 100 177.5 A 77.5 77.5 0 0 0 100 22.5 L 100 17.5 Z"`; + +exports[`RadialGauge utils drawRadialArcPath should draw correct path for narrow radius 1`] = `"M 100 40 A 60 60 0 0 1 100 160 L 100 140 A 40 40 0 0 0 100 60 L 100 40 Z"`; + +exports[`RadialGauge utils drawRadialArcPath should draw correct path for quarter arc 1`] = `"M 100 10 A 90 90 0 0 1 190 100 L 170 100 A 70 70 0 0 0 100 30 L 100 10 Z"`; + +exports[`RadialGauge utils drawRadialArcPath should draw correct path for rounded bars 1`] = `"M 100 10 A 90 90 0 1 1 10 100.00000000000001 A 10 10 0 0 1 30 100.00000000000001 A 70 70 0 1 0 100 30 A 10 10 0 0 1 100 10 Z"`; + +exports[`RadialGauge utils drawRadialArcPath should draw correct path for three quarter arc 1`] = `"M 100 10 A 90 90 0 1 1 10 100.00000000000001 L 30 100.00000000000001 A 70 70 0 1 0 100 30 L 100 10 Z"`; + +exports[`RadialGauge utils drawRadialArcPath should draw correct path for wide bar width 1`] = `"M 100 -5 A 105 105 0 0 1 100 205 L 100 155 A 55 55 0 0 0 100 45 L 100 -5 Z"`; diff --git a/packages/grafana-ui/src/components/RadialGauge/colors.test.ts b/packages/grafana-ui/src/components/RadialGauge/colors.test.ts new file mode 100644 index 00000000000..321e95bb921 --- /dev/null +++ b/packages/grafana-ui/src/components/RadialGauge/colors.test.ts @@ -0,0 +1,306 @@ +import { defaultsDeep } from 'lodash'; + +import { createTheme, FALLBACK_COLOR, Field, FieldDisplay, FieldType, ThresholdsMode } from '@grafana/data'; +import { FieldColorModeId } from '@grafana/schema'; + +import { + buildGradientColors, + colorAtGradientPercent, + getBarEndcapColors, + getEndpointMarkerColors, + getGradientCss, + getGradientStopsForPercent, +} from './colors'; + +export type DeepPartial = { + [P in keyof T]?: DeepPartial; +}; + +describe('RadialGauge color utils', () => { + describe('buildGradientColors', () => { + const createField = (colorMode: FieldColorModeId): Field => + ({ + type: FieldType.number, + name: 'Test Field', + config: { + color: { + mode: colorMode, + }, + thresholds: { + mode: ThresholdsMode.Absolute, + steps: [ + { value: -Infinity, color: 'green' }, + { value: 50, color: 'yellow' }, + { value: 80, color: 'red' }, + ], + }, + }, + values: [70, 40, 30, 90, 55], + }) satisfies Field; + + const buildFieldDisplay = (field: Field, part = {}): FieldDisplay => + defaultsDeep(part, { + field: field.config, + colIndex: 0, + view: { + getFieldDisplayProcessor: jest.fn(() => jest.fn(() => ({ color: undefined }))), + }, + display: { + numeric: 75, + }, + }); + + it('should return the baseColor if gradient is false-y', () => { + expect( + buildGradientColors(false, createTheme(), buildFieldDisplay(createField(FieldColorModeId.Fixed)), '#FF0000') + ).toEqual([ + { color: '#FF0000', percent: 0 }, + { color: '#FF0000', percent: 1 }, + ]); + + expect( + buildGradientColors(undefined, createTheme(), buildFieldDisplay(createField(FieldColorModeId.Fixed)), '#FF0000') + ).toEqual([ + { color: '#FF0000', percent: 0 }, + { color: '#FF0000', percent: 1 }, + ]); + }); + + it('uses the fallback color if no baseColor is set', () => { + expect(buildGradientColors(false, createTheme(), buildFieldDisplay(createField(FieldColorModeId.Fixed)))).toEqual( + [ + { color: FALLBACK_COLOR, percent: 0 }, + { color: FALLBACK_COLOR, percent: 1 }, + ] + ); + }); + + it('should map threshold colors correctly (with baseColor if displayProcessor does not return colors)', () => { + expect( + buildGradientColors( + true, + createTheme(), + buildFieldDisplay(createField(FieldColorModeId.Thresholds), { + view: { getFieldDisplayProcessor: jest.fn(() => jest.fn(() => ({ color: '#444444' }))) }, + }) + ) + ).toMatchSnapshot(); + }); + + it('should map threshold colors correctly (with baseColor if displayProcessor does not return colors)', () => { + expect( + buildGradientColors(true, createTheme(), buildFieldDisplay(createField(FieldColorModeId.Thresholds)), '#FF0000') + ).toMatchSnapshot(); + }); + + it('should return gradient colors for continuous color modes', () => { + expect( + buildGradientColors( + true, + createTheme(), + buildFieldDisplay(createField(FieldColorModeId.ContinuousCividis)), + '#00FF00' + ) + ).toMatchSnapshot(); + }); + + it.each(['dark', 'light'] as const)('should return gradient colors for by-value color mode in %s theme', (mode) => { + expect( + buildGradientColors( + true, + createTheme({ colors: { mode } }), + buildFieldDisplay(createField(FieldColorModeId.ContinuousBlues)) + ) + ).toMatchSnapshot(); + }); + + it.each(['dark', 'light'] as const)('should return gradient colors for fixed color mode in %s theme', (mode) => { + expect( + buildGradientColors( + true, + createTheme({ colors: { mode } }), + buildFieldDisplay(createField(FieldColorModeId.Fixed)), + '#442299' + ) + ).toMatchSnapshot(); + }); + }); + + describe('colorAtGradientPercent', () => { + it('should calculate the color at a given percent in a gradient of two colors', () => { + const gradient = [ + { color: '#ff0000', percent: 0 }, + { color: '#0000ff', percent: 1 }, + ]; + expect(colorAtGradientPercent(gradient, 0).toHexString()).toBe('#ff0000'); + expect(colorAtGradientPercent(gradient, 0.25).toHexString()).toBe('#bf0040'); + expect(colorAtGradientPercent(gradient, 0.5).toHexString()).toBe('#800080'); + expect(colorAtGradientPercent(gradient, 0.75).toHexString()).toBe('#4000bf'); + expect(colorAtGradientPercent(gradient, 1).toHexString()).toBe('#0000ff'); + }); + + it('should calculate the color at a given percent in a gradient of multiple colors', () => { + const gradient = [ + { color: '#ff0000', percent: 0 }, + { color: '#00ff00', percent: 0.5 }, + { color: '#0000ff', percent: 1 }, + ]; + expect(colorAtGradientPercent(gradient, 0).toHexString()).toBe('#ff0000'); + expect(colorAtGradientPercent(gradient, 0.25).toHexString()).toBe('#808000'); + expect(colorAtGradientPercent(gradient, 0.5).toHexString()).toBe('#00ff00'); + expect(colorAtGradientPercent(gradient, 0.75).toHexString()).toBe('#008080'); + expect(colorAtGradientPercent(gradient, 1).toHexString()).toBe('#0000ff'); + }); + + it('will still work if unsorted', () => { + const gradient = [ + { color: '#0000ff', percent: 1 }, + { color: '#00ff00', percent: 0.5 }, + { color: '#ff0000', percent: 0 }, + ]; + expect(colorAtGradientPercent(gradient, 0).toHexString()).toBe('#ff0000'); + expect(colorAtGradientPercent(gradient, 0.25).toHexString()).toBe('#808000'); + expect(colorAtGradientPercent(gradient, 0.5).toHexString()).toBe('#00ff00'); + expect(colorAtGradientPercent(gradient, 0.75).toHexString()).toBe('#008080'); + expect(colorAtGradientPercent(gradient, 1).toHexString()).toBe('#0000ff'); + }); + + it('should not throw an error when percent is outside 0-1 range', () => { + const gradient = [ + { color: '#ff0000', percent: 0 }, + { color: '#0000ff', percent: 1 }, + ]; + expect(colorAtGradientPercent(gradient, -0.5).toHexString()).toBe('#ff0000'); + expect(colorAtGradientPercent(gradient, 1.5).toHexString()).toBe('#0000ff'); + }); + + it('should throw an error when less than two stops are provided', () => { + expect(() => { + colorAtGradientPercent([], 0.5); + }).toThrow('colorAtGradientPercent requires at least two color stops'); + expect(() => { + colorAtGradientPercent([{ color: '#ff0000', percent: 0 }], 0.5); + }).toThrow('colorAtGradientPercent requires at least two color stops'); + }); + }); + + describe('getBarEndcapColors', () => { + it('should return the first and last colors in the gradient', () => { + const gradient = [ + { color: '#ff0000', percent: 0 }, + { color: '#00ff00', percent: 0.5 }, + { color: '#0000ff', percent: 1 }, + ]; + const [startColor, endColor] = getBarEndcapColors(gradient); + expect(startColor).toBe('#ff0000'); + expect(endColor).toBe('#0000ff'); + }); + + it('should return the correct end color based on percent', () => { + const gradient = [ + { color: '#ff0000', percent: 0 }, + { color: '#00ff00', percent: 0.5 }, + { color: '#0000ff', percent: 1 }, + ]; + const [startColor, endColor] = getBarEndcapColors(gradient, 0.25); + expect(startColor).toBe('#ff0000'); + expect(endColor).toBe('#808000'); + }); + + it('should handle gradients with only one colors', () => { + const gradient = [{ color: '#ff0000', percent: 0 }]; + const [startColor, endColor] = getBarEndcapColors(gradient); + expect(startColor).toBe('#ff0000'); + expect(endColor).toBe('#ff0000'); + }); + + it('should throw an error when no colors are provided', () => { + expect(() => { + getBarEndcapColors([]); + }).toThrow('getBarEndcapColors requires at least one color stop'); + }); + }); + + describe('getGradientCss', () => { + it('should return conic-gradient CSS for circle shape', () => { + const gradient = [ + { color: '#ff0000', percent: 0 }, + { color: '#00ff00', percent: 0.5 }, + { color: '#0000ff', percent: 1 }, + ]; + const css = getGradientCss(gradient, 'circle'); + expect(css).toBe('conic-gradient(from 0deg, #ff0000 0.00%, #00ff00 50.00%, #0000ff 100.00%)'); + }); + + it('should return linear-gradient CSS for arc shape', () => { + const gradient = [ + { color: '#ff0000', percent: 0 }, + { color: '#00ff00', percent: 0.5 }, + { color: '#0000ff', percent: 1 }, + ]; + const css = getGradientCss(gradient, 'gauge'); + expect(css).toBe('linear-gradient(90deg, #ff0000 0.00%, #00ff00 50.00%, #0000ff 100.00%)'); + }); + }); + + describe('getEndpointMarkerColors', () => { + it('should return contrasting guide dot colors based on the gradient endpoints and percent', () => { + const gradient = [ + { color: '#000000', percent: 0 }, + { color: '#ffffff', percent: 0.5 }, + { color: '#ffffff', percent: 1 }, + ]; + const [startDotColor, endDotColor] = getEndpointMarkerColors(gradient, 0.35); + expect(startDotColor).toBe('#fbfbfb'); + expect(endDotColor).toBe('#111217'); + }); + }); + + describe('getGradientStopsForPercent', () => { + it('should return the correct gradient stops for a given percent', () => { + const gradient = [ + { color: '#ff0000', percent: 0 }, + { color: '#00ff00', percent: 0.5 }, + { color: '#0000ff', percent: 1 }, + ]; + const [left, right] = getGradientStopsForPercent(gradient, 0.25); + expect(left).toEqual({ color: '#ff0000', percent: 0 }); + expect(right).toEqual({ color: '#00ff00', percent: 0.5 }); + }); + + it('should handle edge cases where percent is at the boundaries', () => { + const gradient = [ + { color: '#ff0000', percent: 0 }, + { color: '#00ff00', percent: 0.5 }, + { color: '#0000ff', percent: 1 }, + ]; + let [left, right] = getGradientStopsForPercent(gradient, 0); + expect(left).toEqual({ color: '#ff0000', percent: 0 }); + expect(right).toEqual({ color: '#ff0000', percent: 0 }); + + [left, right] = getGradientStopsForPercent(gradient, 1); + expect(left).toEqual({ color: '#0000ff', percent: 1 }); + expect(right).toEqual({ color: '#0000ff', percent: 1 }); + }); + + it('should return the same stop if there is one that is equal to the percentage', () => { + const gradient = [ + { color: '#ff0000', percent: 0 }, + { color: '#00ff00', percent: 0.5 }, + { color: '#0000ff', percent: 1 }, + ]; + + let [left, right] = getGradientStopsForPercent(gradient, 0); + expect(left).toEqual({ color: '#ff0000', percent: 0 }); + expect(right).toEqual({ color: '#ff0000', percent: 0 }); + + [left, right] = getGradientStopsForPercent(gradient, 0.5); + expect(left).toEqual({ color: '#00ff00', percent: 0.5 }); + expect(right).toEqual({ color: '#00ff00', percent: 0.5 }); + + [left, right] = getGradientStopsForPercent(gradient, 1); + expect(left).toEqual({ color: '#0000ff', percent: 1 }); + expect(right).toEqual({ color: '#0000ff', percent: 1 }); + }); + }); +}); diff --git a/packages/grafana-ui/src/components/RadialGauge/colors.ts b/packages/grafana-ui/src/components/RadialGauge/colors.ts new file mode 100644 index 00000000000..61160a9f826 --- /dev/null +++ b/packages/grafana-ui/src/components/RadialGauge/colors.ts @@ -0,0 +1,195 @@ +import tinycolor from 'tinycolor2'; + +import { colorManipulator, FALLBACK_COLOR, FieldDisplay, getFieldColorMode, GrafanaTheme2 } from '@grafana/data'; +import { FieldColorModeId } from '@grafana/schema'; + +import { GradientStop, RadialShape } from './types'; +import { getFieldConfigMinMax, getFieldDisplayProcessor, getValuePercentageForValue } from './utils'; + +export function buildGradientColors( + gradient = false, + theme: GrafanaTheme2, + fieldDisplay: FieldDisplay, + baseColor = fieldDisplay.display.color ?? FALLBACK_COLOR +): GradientStop[] { + if (!gradient) { + return [ + { color: baseColor, percent: 0 }, + { color: baseColor, percent: 1 }, + ]; + } + + const colorMode = getFieldColorMode(fieldDisplay.field.color?.mode); + + // thresholds get special handling + if (colorMode.id === FieldColorModeId.Thresholds) { + const displayProcessor = getFieldDisplayProcessor(fieldDisplay); + const [min, max] = getFieldConfigMinMax(fieldDisplay); + const thresholds = fieldDisplay.field.thresholds?.steps ?? []; + + const result: Array<{ color: string; percent: number }> = [ + { color: displayProcessor(min).color ?? baseColor, percent: 0 }, + ]; + + for (const threshold of thresholds) { + if (threshold.value > min && threshold.value < max) { + const percent = (threshold.value - min) / (max - min); + result.push({ color: theme.visualization.getColorByName(threshold.color), percent }); + } + } + + result.push({ color: displayProcessor(max).color ?? baseColor, percent: 1 }); + + return result; + } + + // Handle continuous color modes before other by-value modes + if (colorMode.isContinuous && colorMode.getColors) { + const colors = colorMode.getColors(theme); + return colors.map((color, idx) => ({ color, percent: idx / (colors.length - 1) })); + } + + // For value-based colors, we want to stay more true to the specific color, + // so a radial gradient that adds a bit of light and shade works best + if (colorMode.isByValue) { + const darkerColor = tinycolor(baseColor).darken(5); + const lighterColor = tinycolor(baseColor).spin(20).lighten(10); + + const color1 = theme.isDark ? lighterColor : darkerColor; + const color2 = theme.isDark ? darkerColor : lighterColor; + + return [ + { color: color1.toString(), percent: 0 }, + { color: color2.toString(), percent: 0.6 }, + { color: color2.toString(), percent: 1 }, + ]; + } + + // For fixed / palette based color scales we can create a more hue and light + // based linear gradient that we rotate with the value + const darkerColor = tinycolor(baseColor) + .spin(-20) + .darken(theme.isDark ? 15 : 5); + const lighterColor = tinycolor(baseColor).saturate(20).spin(20).brighten(10).lighten(10); + + const underlyingGradient = [ + { color: theme.isDark ? darkerColor.toString() : lighterColor.toString(), percent: 0 }, + { color: theme.isDark ? lighterColor.toString() : darkerColor.toString(), percent: 1 }, + ]; + + // rotate the gradient so that the highest contrasting point is the value, depending on theme. + const valuePercent = getValuePercentageForValue(fieldDisplay); + const startColor = theme.isDark + ? colorAtGradientPercent(underlyingGradient, 1 - valuePercent).toHexString() + : underlyingGradient[0].color; + const endColor = theme.isDark + ? underlyingGradient[1].color + : colorAtGradientPercent(underlyingGradient, valuePercent).toHexString(); + return [ + { color: startColor, percent: 0 }, + { color: endColor, percent: valuePercent }, + { color: endColor, percent: 1 }, + ]; +} + +/** + * get the relevant gradient stops surrounding a given percentage. could be same stop if the + * percent matches a stop exactly. + * + * @param sortedGradientStops - gradient stops sorted by percent + * @param percent - percentage 0..1 + * @returns {[GradientStop, GradientStop]} - the two gradient stops surrounding the given percentage + */ +export function getGradientStopsForPercent( + sortedGradientStops: GradientStop[], + percent: number +): [GradientStop, GradientStop] { + if (percent <= 0) { + return [sortedGradientStops[0], sortedGradientStops[0]]; + } + if (percent >= 1) { + const last = sortedGradientStops.length - 1; + return [sortedGradientStops[last], sortedGradientStops[last]]; + } + + // find surrounding stops using binary search + let lo = 0; + let hi = sortedGradientStops.length - 1; + while (lo + 1 < hi) { + const mid = (lo + hi) >> 1; + if (percent === sortedGradientStops[mid].percent) { + return [sortedGradientStops[mid], sortedGradientStops[mid]]; + } + + if (percent < sortedGradientStops[mid].percent) { + hi = mid; + } else { + lo = mid; + } + } + return [sortedGradientStops[lo], sortedGradientStops[hi]]; +} + +/** + * @alpha - perhaps this should go in colorManipulator.ts + * Given color stops (each with a color and percentage 0..1) returns the color at a given percentage. + * Uses tinycolor.mix for interpolation. + * @params stops - array of color stops (percentages 0..1) + * @params percent - percentage 0..1 + * @returns color at the given percentage + */ +export function colorAtGradientPercent(stops: GradientStop[], percent: number): tinycolor.Instance { + if (!stops || stops.length < 2) { + throw new Error('colorAtGradientPercent requires at least two color stops'); + } + + const sorted = stops + .map((s: GradientStop): GradientStop => ({ color: s.color, percent: Math.min(Math.max(0, s.percent), 1) })) + .sort((a: GradientStop, b: GradientStop) => a.percent - b.percent); + + const [left, right] = getGradientStopsForPercent(sorted, percent); + const range = right.percent - left.percent; + const t = range === 0 ? 0 : (percent - left.percent) / range; // 0..1 + return tinycolor.mix(left.color, right.color, t * 100); +} + +export function getBarEndcapColors(gradientStops: GradientStop[], percent = 1): [string, string] { + if (gradientStops.length === 0) { + throw new Error('getBarEndcapColors requires at least one color stop'); + } + + const startColor = gradientStops[0].color; + let endColor = gradientStops[gradientStops.length - 1].color; + + // if we have a percentageFilled, use it to get a the correct end color based on where the bar terminates + if (gradientStops.length >= 2) { + const endColorByPercentage = colorAtGradientPercent(gradientStops, percent); + endColor = + endColorByPercentage.getAlpha() === 1 ? endColorByPercentage.toHexString() : endColorByPercentage.toHex8String(); + } + return [startColor, endColor]; +} + +export function getGradientCss(gradientStops: GradientStop[], shape: RadialShape): string { + const colorStrings = gradientStops.map((stop) => `${stop.color} ${(stop.percent * 100).toFixed(2)}%`); + if (shape === 'circle') { + return `conic-gradient(from 0deg, ${colorStrings.join(', ')})`; + } + return `linear-gradient(90deg, ${colorStrings.join(', ')})`; +} + +// the theme does not make the full palette available to us, and we +// don't want transparent colors which our grays usually have. +const GRAY_05 = '#111217'; +const GRAY_90 = '#fbfbfb'; +const CONTRAST_THRESHOLD_MAX = 4.5; +const getGuideDotColor = (color: string): string => { + const darkColor = GRAY_05; + const lightColor = GRAY_90; + return colorManipulator.getContrastRatio(darkColor, color) >= CONTRAST_THRESHOLD_MAX ? darkColor : lightColor; +}; + +export function getEndpointMarkerColors(gradientStops: GradientStop[], percent = 1): [string, string] { + const [startColor, endColor] = getBarEndcapColors(gradientStops, percent); + return [getGuideDotColor(startColor), getGuideDotColor(endColor)]; +} diff --git a/packages/grafana-ui/src/components/RadialGauge/effects.tsx b/packages/grafana-ui/src/components/RadialGauge/effects.tsx index 354a68a25ba..c48307f177e 100644 --- a/packages/grafana-ui/src/components/RadialGauge/effects.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/effects.tsx @@ -1,15 +1,18 @@ import { GrafanaTheme2 } from '@grafana/data'; -import { GaugeDimensions } from './utils'; +import { RadialGaugeDimensions } from './types'; export interface GlowGradientProps { id: string; barWidth: number; } +const MIN_GLOW_SIZE = 0.75; +const GLOW_FACTOR = 0.08; + export function GlowGradient({ id, barWidth }: GlowGradientProps) { // 0.75 is the minimum glow size, and it scales with bar width - const glowSize = 0.75 + barWidth * 0.08; + const glowSize = MIN_GLOW_SIZE + barWidth * GLOW_FACTOR; return ( @@ -22,56 +25,19 @@ export function GlowGradient({ id, barWidth }: GlowGradientProps) { ); } -export function SpotlightGradient({ - id, - dimensions, - roundedBars, - angle, - theme, -}: { - id: string; - dimensions: GaugeDimensions; - angle: number; - roundedBars: boolean; - theme: GrafanaTheme2; -}) { - const angleRadian = ((angle - 90) * Math.PI) / 180; - - let x1 = dimensions.centerX + dimensions.radius * Math.cos(angleRadian - 0.2); - let y1 = dimensions.centerY + dimensions.radius * Math.sin(angleRadian - 0.2); - let x2 = dimensions.centerX + dimensions.radius * Math.cos(angleRadian); - let y2 = dimensions.centerY + dimensions.radius * Math.sin(angleRadian); - - if (theme.isLight) { - return ( - - - - - - ); - } - - return ( - - - - {roundedBars && } - - ); -} +const CENTER_GLOW_OPACITY = 0.15; export function CenterGlowGradient({ gaugeId, color }: { gaugeId: string; color: string }) { return ( - - + + ); } export interface CenterGlowProps { - dimensions: GaugeDimensions; + dimensions: RadialGaugeDimensions; gaugeId: string; color?: string; } @@ -82,8 +48,8 @@ export function MiddleCircleGlow({ dimensions, gaugeId, color }: CenterGlowProps return ( <> - - + + @@ -93,3 +59,36 @@ export function MiddleCircleGlow({ dimensions, gaugeId, color }: CenterGlowProps ); } + +export function SpotlightGradient({ + id, + dimensions, + roundedBars, + angle, + theme, +}: { + id: string; + dimensions: RadialGaugeDimensions; + angle: number; + roundedBars: boolean; + theme: GrafanaTheme2; +}) { + if (theme.isLight) { + return null; + } + + const angleRadian = ((angle - 90) * Math.PI) / 180; + + let x1 = dimensions.centerX + dimensions.radius * Math.cos(angleRadian - 0.2); + let y1 = dimensions.centerY + dimensions.radius * Math.sin(angleRadian - 0.2); + let x2 = dimensions.centerX + dimensions.radius * Math.cos(angleRadian); + let y2 = dimensions.centerY + dimensions.radius * Math.sin(angleRadian); + + return ( + + + + {roundedBars && } + + ); +} diff --git a/packages/grafana-ui/src/components/RadialGauge/types.ts b/packages/grafana-ui/src/components/RadialGauge/types.ts new file mode 100644 index 00000000000..cc233dd524c --- /dev/null +++ b/packages/grafana-ui/src/components/RadialGauge/types.ts @@ -0,0 +1,25 @@ +export type RadialTextMode = 'auto' | 'value_and_name' | 'value' | 'name' | 'none'; +export type RadialShape = 'circle' | 'gauge'; + +export interface RadialGaugeDimensions { + margin: number; + radius: number; + centerX: number; + centerY: number; + barWidth: number; + endAngle?: number; + barIndex: number; + thresholdsBarRadius: number; + thresholdsBarWidth: number; + thresholdsBarSpacing: number; + scaleLabelsFontSize: number; + scaleLabelsSpacing: number; + scaleLabelsRadius: number; + gaugeBottomY: number; +} + +/** @alpha - perhaps this should go in @grafana/data */ +export interface GradientStop { + color: string; + percent: number; +} diff --git a/packages/grafana-ui/src/components/RadialGauge/utils.test.ts b/packages/grafana-ui/src/components/RadialGauge/utils.test.ts index 5e3f34d62cd..b9b2e4ad8f3 100644 --- a/packages/grafana-ui/src/components/RadialGauge/utils.test.ts +++ b/packages/grafana-ui/src/components/RadialGauge/utils.test.ts @@ -1,24 +1,111 @@ -import { FieldDisplay } from '@grafana/data'; +import { DataFrameView, FieldDisplay } from '@grafana/data'; import type { RadialGaugeProps } from './RadialGauge'; -import { calculateDimensions, toRad, getValueAngleForValue } from './utils'; +import { RadialGaugeDimensions } from './types'; +import { + calculateDimensions, + toRad, + getValueAngleForValue, + drawRadialArcPath, + getFieldConfigMinMax, + getFieldDisplayProcessor, + getAngleBetweenSegments, + getOptimalSegmentCount, +} from './utils'; describe('RadialGauge utils', () => { - function calc(overrides: Partial = {}) { - return calculateDimensions( - overrides.width ?? 200, - overrides.height ?? 200, - overrides.shape === 'gauge' ? 110 : 360, - overrides.glowBar ?? false, - overrides.roundedBars ?? false, - overrides.barWidthFactor ?? 0.4, - overrides.barIndex ?? 0, - overrides.thresholdsBar ?? false, - overrides.showScaleLabels ?? false - ); - } + describe('getFieldDisplayProcessor', () => { + it('should return display processor from view when available', () => { + const mockProcessor = jest.fn(); + const mockView = { + getFieldDisplayProcessor: jest.fn().mockReturnValue(mockProcessor), + } as unknown as DataFrameView; + + const fieldDisplay: FieldDisplay = { + display: { numeric: 50, text: '50', color: 'blue' }, + field: {}, + view: mockView, + colIndex: 0, + rowIndex: 0, + name: 'test', + getLinks: () => [], + hasLinks: false, + }; + + const dp = getFieldDisplayProcessor(fieldDisplay); + expect(dp).toBe(mockProcessor); + expect(mockView.getFieldDisplayProcessor).toHaveBeenCalledWith(0); + }); + + it('should return default display processor when view is not available', () => { + const fieldDisplay: FieldDisplay = { + display: { numeric: 50, text: '50', color: 'blue' }, + field: {}, + view: undefined, + colIndex: 0, + rowIndex: 0, + name: 'test', + getLinks: () => [], + hasLinks: false, + }; + + const dp = getFieldDisplayProcessor(fieldDisplay); + expect(dp).toBeDefined(); + expect(typeof dp).toBe('function'); + }); + }); + + describe('getFieldConfigMinMax', () => { + it('should return min and max from field config when defined', () => { + const fieldDisplay: FieldDisplay = { + display: { numeric: 50, text: '50', color: 'blue' }, + field: { min: 10, max: 90 }, + view: undefined, + colIndex: 0, + rowIndex: 0, + name: 'test', + getLinks: () => [], + hasLinks: false, + }; + + const [min, max] = getFieldConfigMinMax(fieldDisplay); + expect(min).toBe(10); + expect(max).toBe(90); + }); + + it('should return default min and max when not defined in field config', () => { + const fieldDisplay: FieldDisplay = { + display: { numeric: 50, text: '50', color: 'blue' }, + field: {}, + view: undefined, + colIndex: 0, + rowIndex: 0, + name: 'test', + getLinks: () => [], + hasLinks: false, + }; + + const [min, max] = getFieldConfigMinMax(fieldDisplay); + expect(min).toBe(0); + expect(max).toBe(100); + }); + }); describe('calculateDimensions', () => { + function calc(overrides: Partial = {}) { + return calculateDimensions( + overrides.width ?? 200, + overrides.height ?? 200, + overrides.shape === 'gauge' ? 110 : 360, + overrides.glowBar ?? false, + overrides.roundedBars ?? false, + overrides.barWidthFactor ?? 0.4, + overrides.barIndex ?? 0, + overrides.thresholdsBar ?? false, + overrides.showScaleLabels ?? false + ); + } + it('should calculate basic dimensions for a square gauge', () => { const result = calc(); @@ -194,4 +281,84 @@ describe('RadialGauge utils', () => { expect(result.angle).toBe(240); }); }); + + describe('drawRadialArcPath', () => { + const defaultDims: RadialGaugeDimensions = Object.freeze({ + centerX: 100, + centerY: 100, + radius: 80, + barWidth: 20, + margin: 0, + barIndex: 0, + thresholdsBarWidth: 0, + thresholdsBarSpacing: 0, + thresholdsBarRadius: 0, + scaleLabelsFontSize: 0, + scaleLabelsSpacing: 0, + scaleLabelsRadius: 0, + gaugeBottomY: 0, + }); + + it.each([ + { description: 'quarter arc', startAngle: 0, endAngle: 90 }, + { description: 'half arc', startAngle: 0, endAngle: 180 }, + { description: 'three quarter arc', startAngle: 0, endAngle: 270 }, + { description: 'rounded bars', startAngle: 0, endAngle: 270, roundedBars: true }, + { description: 'wide bar width', startAngle: 0, endAngle: 180, dimensions: { barWidth: 50 } }, + { description: 'narrow bar width', startAngle: 0, endAngle: 180, dimensions: { barWidth: 5 } }, + { description: 'narrow radius', startAngle: 0, endAngle: 180, dimensions: { radius: 50 } }, + { + description: 'center x and y', + startAngle: 0, + endAngle: 360, + roundedBars: true, + dimensions: { centerX: 150, centerY: 200 }, + }, + ])(`should draw correct path for $description`, ({ startAngle, endAngle, dimensions, roundedBars }) => { + const path = drawRadialArcPath(startAngle, endAngle, { ...defaultDims, ...dimensions }, roundedBars); + expect(path).toMatchSnapshot(); + }); + + describe('edge cases', () => { + it('should adjust 360deg or greater arcs to avoid SVG rendering issues', () => { + expect(drawRadialArcPath(0, 360, defaultDims)).toEqual(drawRadialArcPath(0, 359.99, defaultDims)); + expect(drawRadialArcPath(0, 380, defaultDims)).toEqual(drawRadialArcPath(0, 380, defaultDims)); + }); + + it('should return empty string if inner radius collapses to zero or below', () => { + const smallRadiusDims = { ...defaultDims, radius: 5, barWidth: 20 }; + expect(drawRadialArcPath(0, 180, smallRadiusDims)).toBe(''); + }); + }); + }); + + describe('getAngleBetweenSegments', () => { + it('should calculate angle between segments based on spacing and count', () => { + expect(getAngleBetweenSegments(2, 10, 360)).toBe(48); + expect(getAngleBetweenSegments(5, 15, 180)).toBe(40); + }); + }); + + describe('getOptimalSegmentCount', () => { + it('should adjust segment count based on dimensions and spacing', () => { + const dimensions: RadialGaugeDimensions = { + centerX: 100, + centerY: 100, + radius: 80, + barWidth: 20, + margin: 0, + barIndex: 0, + thresholdsBarWidth: 0, + thresholdsBarSpacing: 0, + thresholdsBarRadius: 0, + scaleLabelsFontSize: 0, + scaleLabelsSpacing: 0, + scaleLabelsRadius: 0, + gaugeBottomY: 0, + }; + + expect(getOptimalSegmentCount(dimensions, 2, 10, 360)).toBe(8); + expect(getOptimalSegmentCount(dimensions, 1, 5, 360)).toBe(5); + }); + }); }); diff --git a/packages/grafana-ui/src/components/RadialGauge/utils.ts b/packages/grafana-ui/src/components/RadialGauge/utils.ts index 44f767d89b2..e26cf5eed2a 100644 --- a/packages/grafana-ui/src/components/RadialGauge/utils.ts +++ b/packages/grafana-ui/src/components/RadialGauge/utils.ts @@ -1,11 +1,38 @@ -import { FieldDisplay } from '@grafana/data'; +import { FieldDisplay, getDisplayProcessor } from '@grafana/data'; -export function getValueAngleForValue(fieldDisplay: FieldDisplay, startAngle: number, endAngle: number) { - const angleRange = (360 % (startAngle === 0 ? 1 : startAngle)) + endAngle; +import { RadialGaugeDimensions } from './types'; + +export function getFieldDisplayProcessor(displayValue: FieldDisplay) { + if (displayValue.view && displayValue.colIndex != null) { + const dp = displayValue.view.getFieldDisplayProcessor(displayValue.colIndex); + if (dp) { + return dp; + } + } + + return getDisplayProcessor(); +} + +export function getFieldConfigMinMax(fieldDisplay: FieldDisplay) { const min = fieldDisplay.field.min ?? 0; const max = fieldDisplay.field.max ?? 100; + return [min, max]; +} - let angle = ((fieldDisplay.display.numeric - min) / (max - min)) * angleRange; +export function getValuePercentageForValue(fieldDisplay: FieldDisplay, value = fieldDisplay.display.numeric) { + const [min, max] = getFieldConfigMinMax(fieldDisplay); + return (value - min) / (max - min); +} + +export function getValueAngleForValue( + fieldDisplay: FieldDisplay, + startAngle: number, + endAngle: number, + value = fieldDisplay.display.numeric +) { + const angleRange = (360 % (startAngle === 0 ? 1 : startAngle)) + endAngle; + + let angle = getValuePercentageForValue(fieldDisplay, value) * angleRange; if (angle > angleRange) { angle = angleRange; @@ -26,24 +53,19 @@ export function toRad(angle: number) { return ((angle - 90) * Math.PI) / 180; } -export interface GaugeDimensions { - margin: number; - radius: number; - centerX: number; - centerY: number; - barWidth: number; - endAngle?: number; - barIndex: number; - thresholdsBarRadius: number; - thresholdsBarWidth: number; - thresholdsBarSpacing: number; - showScaleLabels?: boolean; - scaleLabelsFontSize: number; - scaleLabelsSpacing: number; - scaleLabelsRadius: number; - gaugeBottomY: number; -} - +/** + * returns the calculated dimensions for the radial gauge + * @param width + * @param height + * @param endAngle + * @param glow + * @param roundedBars + * @param barWidthFactor + * @param barIndex + * @param thresholdBar + * @param showScaleLabels + * @returns {RadialGaugeDimensions} + */ export function calculateDimensions( width: number, height: number, @@ -54,7 +76,7 @@ export function calculateDimensions( barIndex: number, thresholdBar?: boolean, showScaleLabels?: boolean -): GaugeDimensions { +): RadialGaugeDimensions { const yMaxAngle = endAngle > 180 ? 180 : endAngle; let margin = 0; @@ -97,6 +119,7 @@ export function calculateDimensions( maxRadiusW -= labelsSize; maxRadiusH -= labelsSize; + // FIXME: needs coverage // For gauges the max label needs a bit more vertical space so that it does not get clipped if (maxRadiusIsLimitedByHeight && endAngle < 180) { const amount = outerRadius * 0.07; @@ -155,3 +178,105 @@ export function toCartesian(centerX: number, centerY: number, radius: number, an y: centerY + radius * Math.sin(radian), }; } + +export function drawRadialArcPath( + startAngle: number, + endAngle: number, + dimensions: RadialGaugeDimensions, + roundedBars?: boolean +): string { + const { radius, centerX, centerY, barWidth } = dimensions; + + // For some reason a 100% full arc cannot be rendered + if (endAngle >= 360) { + endAngle = 359.99; + } + + const startRadians = toRad(startAngle); + const endRadians = toRad(startAngle + endAngle); + + const largeArc = endAngle > 180 ? 1 : 0; + + const outerR = radius + barWidth / 2; + const innerR = Math.max(0, radius - barWidth / 2); + if (innerR <= 0) { + return ''; // cannot draw arc with 0 inner radius + } + + // get points for both an inner and outer arc. we draw + // the arc entirely with a path's fill instead of using stroke + // so that it can be used as a clip-path. + const ox1 = centerX + outerR * Math.cos(startRadians); + const oy1 = centerY + outerR * Math.sin(startRadians); + const ox2 = centerX + outerR * Math.cos(endRadians); + const oy2 = centerY + outerR * Math.sin(endRadians); + + const ix1 = centerX + innerR * Math.cos(startRadians); + const iy1 = centerY + innerR * Math.sin(startRadians); + const ix2 = centerX + innerR * Math.cos(endRadians); + const iy2 = centerY + innerR * Math.sin(endRadians); + + // calculate the cap width in case we're drawing rounded bars + const capR = barWidth / 2; + + const pathParts = [ + // start at outer start + 'M', + ox1, + oy1, + // outer arc from start to end (clockwise) + 'A', + outerR, + outerR, + 0, + largeArc, + 1, + ox2, + oy2, + ]; + + if (roundedBars) { + // rounded end cap: small arc connecting outer end to inner end + pathParts.push('A', capR, capR, 0, 0, 1, ix2, iy2); + } else { + // straight line to inner end (square butt) + pathParts.push('L', ix2, iy2); + } + + // inner arc from end back to start (counter-clockwise) + pathParts.push('A', innerR, innerR, 0, largeArc, 0, ix1, iy1); + + if (roundedBars) { + // rounded start cap: small arc connecting inner start back to outer start + pathParts.push('A', capR, capR, 0, 0, 1, ox1, oy1); + } else { + // straight line back to outer start (square butt) + pathParts.push('L', ox1, oy1); + } + + pathParts.push('Z'); + + return pathParts.join(' '); +} + +export function getAngleBetweenSegments(segmentSpacing: number, segmentCount: number, range: number) { + // Max spacing is 8 degrees between segments + // Changing this constant could be considered a breaking change + const maxAngleBetweenSegments = Math.max(range / 1.5 / segmentCount, 2); + return segmentSpacing * maxAngleBetweenSegments; +} + +export function getOptimalSegmentCount( + dimensions: RadialGaugeDimensions, + segmentSpacing: number, + segmentCount: number, + range: number +) { + const angleBetweenSegments = getAngleBetweenSegments(segmentSpacing, segmentCount, range); + + const innerRadius = dimensions.radius - dimensions.barWidth / 2; + const circumference = Math.PI * innerRadius * 2 * (range / 360); + const maxSegments = Math.floor(circumference / (angleBetweenSegments + 3)); + + return Math.min(maxSegments, segmentCount); +} diff --git a/public/app/plugins/panel/radialbar/EffectsEditor.tsx b/public/app/plugins/panel/radialbar/EffectsEditor.tsx index d7a0f03cbc7..a3c26beca90 100644 --- a/public/app/plugins/panel/radialbar/EffectsEditor.tsx +++ b/public/app/plugins/panel/radialbar/EffectsEditor.tsx @@ -44,11 +44,6 @@ export function EffectsEditor(props: StandardEditorProps) { value={!!props.value?.gradient} onChange={(e) => props.onChange({ ...props.value, gradient: e.currentTarget.checked })} /> - props.onChange({ ...props.value, rounded: e.currentTarget.checked })} - /> ) { value={!!props.value?.centerGlow} onChange={(e) => props.onChange({ ...props.value, centerGlow: e.currentTarget.checked })} /> - props.onChange({ ...props.value, spotlight: e.currentTarget.checked })} - /> ); } diff --git a/public/app/plugins/panel/radialbar/RadialBarPanel.tsx b/public/app/plugins/panel/radialbar/RadialBarPanel.tsx index 86235a3bf68..03232406463 100644 --- a/public/app/plugins/panel/radialbar/RadialBarPanel.tsx +++ b/public/app/plugins/panel/radialbar/RadialBarPanel.tsx @@ -37,11 +37,10 @@ export function RadialBarPanel({ width={width} height={height} barWidthFactor={options.barWidthFactor} - gradient={options.effects?.gradient ? 'auto' : 'none'} - spotlight={options.effects?.spotlight} + gradient={options.effects?.gradient} glowBar={options.effects?.barGlow} glowCenter={options.effects?.centerGlow} - roundedBars={options.effects?.rounded} + roundedBars={options.barShape === 'rounded'} vizCount={valueProps.count} shape={options.shape} segmentCount={options.segmentCount} @@ -51,6 +50,7 @@ export function RadialBarPanel({ alignmentFactors={valueProps.alignmentFactors} valueManualFontSize={options.text?.valueSize} nameManualFontSize={options.text?.titleSize} + endpointMarker={options.endpointMarker !== 'none' ? options.endpointMarker : undefined} onClick={menuProps.openMenu} /> ); diff --git a/public/app/plugins/panel/radialbar/module.tsx b/public/app/plugins/panel/radialbar/module.tsx index 12aff4966f7..3dfcb3e3d5f 100644 --- a/public/app/plugins/panel/radialbar/module.tsx +++ b/public/app/plugins/panel/radialbar/module.tsx @@ -69,6 +69,36 @@ export const plugin = new PanelPlugin(RadialBarPanel) }, }); + builder.addRadio({ + path: 'barShape', + name: t('radialbar.config.bar-shape', 'Bar Style'), + category, + defaultValue: defaultOptions.barShape, + settings: { + options: [ + { value: 'flat', label: t('radialbar.config.bar-shape-flat', 'Flat') }, + { value: 'rounded', label: t('radialbar.config.bar-shape-rounded', 'Rounded') }, + ], + }, + showIf: (options) => options.segmentCount === 1, + }); + + builder.addRadio({ + path: 'endpointMarker', + name: t('radialbar.config.endpoint-marker', 'Endpoint marker'), + description: t('radialbar.config.endpoint-marker-description', 'Glow is only supported in dark mode'), + category, + defaultValue: defaultOptions.endpointMarker, + settings: { + options: [ + { value: 'point', label: t('radialbar.config.endpoint-marker-point', 'Point') }, + { value: 'glow', label: t('radialbar.config.endpoint-marker-glow', 'Glow') }, + { value: 'none', label: t('radialbar.config.endpoint-marker-none', 'None') }, + ], + }, + showIf: (options) => options.barShape === 'rounded' && options.segmentCount === 1, + }); + builder.addBooleanSwitch({ path: 'sparkline', name: t('radialbar.config.sparkline', 'Show sparkline'), diff --git a/public/app/plugins/panel/radialbar/panelcfg.cue b/public/app/plugins/panel/radialbar/panelcfg.cue index a37982dcfe1..c959512c0b8 100644 --- a/public/app/plugins/panel/radialbar/panelcfg.cue +++ b/public/app/plugins/panel/radialbar/panelcfg.cue @@ -27,10 +27,8 @@ composableKinds: PanelCfg: { schema: { GaugePanelEffects: { barGlow?: bool | *false - spotlight?: bool | *false - rounded?: bool | *false centerGlow?: bool | *false - gradient?: bool | *true + gradient?: bool | *true } @cuetsy(kind="interface") Options: { @@ -42,6 +40,8 @@ composableKinds: PanelCfg: { sparkline?: bool | *true shape: "circle" | *"gauge" barWidthFactor: number | *0.5 + barShape: "flat" | "rounded" | *"flat" + endpointMarker?: "point" | "glow" | "none" | *"point" effects: GaugePanelEffects | *{} } @cuetsy(kind="interface") } diff --git a/public/app/plugins/panel/radialbar/panelcfg.gen.ts b/public/app/plugins/panel/radialbar/panelcfg.gen.ts index 24915c62ef1..e050a044f77 100644 --- a/public/app/plugins/panel/radialbar/panelcfg.gen.ts +++ b/public/app/plugins/panel/radialbar/panelcfg.gen.ts @@ -14,21 +14,19 @@ export interface GaugePanelEffects { barGlow?: boolean; centerGlow?: boolean; gradient?: boolean; - rounded?: boolean; - spotlight?: boolean; } export const defaultGaugePanelEffects: Partial = { barGlow: false, centerGlow: false, gradient: true, - rounded: false, - spotlight: false, }; export interface Options extends common.SingleStatBaseOptions { + barShape: ('flat' | 'rounded'); barWidthFactor: number; effects: GaugePanelEffects; + endpointMarker?: ('point' | 'glow' | 'none'); segmentCount: number; segmentSpacing: number; shape: ('circle' | 'gauge'); @@ -38,8 +36,10 @@ export interface Options extends common.SingleStatBaseOptions { } export const defaultOptions: Partial = { + barShape: 'flat', barWidthFactor: 0.5, effects: {}, + endpointMarker: 'point', segmentCount: 1, segmentSpacing: 0.3, shape: 'gauge', diff --git a/public/app/plugins/panel/radialbar/suggestions.ts b/public/app/plugins/panel/radialbar/suggestions.ts index 00896ae8458..eab5334ef40 100644 --- a/public/app/plugins/panel/radialbar/suggestions.ts +++ b/public/app/plugins/panel/radialbar/suggestions.ts @@ -18,19 +18,6 @@ const withDefaults = ( } }, }, - // styles: [{ - // name: t('gauge.suggestions.style.circular', 'Glowing'), - // options: { - // effects: { - // rounded: true, - // barGlow: true, - // centerGlow: true, - // spotlight: true, - // }, - // }, - // }, { - // name: t('gauge.suggestions.style.simple', 'Simple'), - // }] } satisfies VisualizationSuggestion); const MAX_GAUGES = 10; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index a4e39d534a3..d97ce1128ba 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -7963,11 +7963,7 @@ "suggestions": { "arc": "Gauge", "circular": "Circular gauge", - "no-thresholds": "Gauge - no thresholds", - "style": { - "circular": "Glowing", - "simple": "Simple" - } + "no-thresholds": "Gauge - no thresholds" }, "threshold": "Threshold {{value}}" }, @@ -12493,16 +12489,21 @@ }, "radialbar": { "config": { + "bar-shape": "Bar Style", + "bar-shape-flat": "Flat", + "bar-shape-rounded": "Rounded", "bar-width": "Bar width", "effects": { "bar-glow": "Bar glow", "center-glow": "Center glow", "gradient": "Gradient", - "label": "Effects", - "rounded-bars": "Rounded bars", - "spotlight": "Spotlight", - "spotlight-tooltip": "Only visible in dark themes" + "label": "Effects" }, + "endpoint-marker": "Endpoint marker", + "endpoint-marker-description": "Glow is only supported in dark mode", + "endpoint-marker-glow": "Glow", + "endpoint-marker-none": "None", + "endpoint-marker-point": "Point", "segment-count": "Segments", "segment-spacing": "Segment spacing", "shape": "Style", From 6daa7ff72911e5fc3938adf500db7bafec5f5ef6 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 19 Dec 2025 16:05:46 -0500 Subject: [PATCH 274/297] Clean up Schema Inspector feature code (#115514) Co-authored-by: Alex Spencer <52186778+alexjonspencer1@users.noreply.github.com> --- .../SchemaInspector/SchemaInspectorPanel.tsx | 80 ++-- .../SqlExpressions/SqlExpr.test.tsx | 90 +---- .../components/SqlExpressions/SqlExpr.tsx | 344 ++++++------------ .../SqlExpressions/SqlExprContext.test.tsx | 88 +++++ .../SqlExpressions/SqlExprContext.tsx | 38 ++ .../SqlExpressions/SqlQueryActions.test.tsx | 137 +++++++ .../SqlExpressions/SqlQueryActions.tsx | 91 +++++ public/locales/en-US/grafana.json | 9 +- 8 files changed, 498 insertions(+), 379 deletions(-) create mode 100644 public/app/features/expressions/components/SqlExpressions/SqlExprContext.test.tsx create mode 100644 public/app/features/expressions/components/SqlExpressions/SqlExprContext.tsx create mode 100644 public/app/features/expressions/components/SqlExpressions/SqlQueryActions.test.tsx create mode 100644 public/app/features/expressions/components/SqlExpressions/SqlQueryActions.tsx diff --git a/public/app/features/expressions/components/SqlExpressions/SchemaInspector/SchemaInspectorPanel.tsx b/public/app/features/expressions/components/SqlExpressions/SchemaInspector/SchemaInspectorPanel.tsx index c84bf0d4223..a065bbc779f 100644 --- a/public/app/features/expressions/components/SqlExpressions/SchemaInspector/SchemaInspectorPanel.tsx +++ b/public/app/features/expressions/components/SqlExpressions/SchemaInspector/SchemaInspectorPanel.tsx @@ -1,25 +1,24 @@ import { css } from '@emotion/css'; -import { useState, useMemo } from 'react'; +import { useMemo, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { t, Trans } from '@grafana/i18n'; import { - Stack, - Tab, - TabsBar, - TabContent, - Icon, + Alert, Badge, - Text, - useStyles2, + Icon, InteractiveTable, ScrollContainer, - Alert, Spinner, - IconButton, + Stack, + Tab, + TabContent, + TabsBar, + Text, + useStyles2, } from '@grafana/ui'; -import { SQLSchemas, SQLSchemaField, SQLSchemaData } from '../hooks/useSQLSchemas'; +import { SQLSchemaData, SQLSchemaField, SQLSchemas } from '../hooks/useSQLSchemas'; import { getFieldTypeIcon } from './utils'; @@ -33,10 +32,9 @@ interface SchemaInspectorPanelProps { schemas: SQLSchemas | null; loading: boolean; error: Error | null; - onClose: () => void; } -export const SchemaInspectorPanel = ({ schemas, loading, error, onClose }: SchemaInspectorPanelProps) => { +export const SchemaInspectorPanel = ({ schemas, loading, error }: SchemaInspectorPanelProps) => { const styles = useStyles2(getStyles); const schemaResponse: SQLSchemas = schemas ?? {}; @@ -192,32 +190,21 @@ export const SchemaInspectorPanel = ({ schemas, loading, error, onClose }: Schem }; return ( -
-
- {refIds.length > 0 && ( - - {refIds.map((refId) => ( - setSelectedTab(refId)} - /> - ))} - - )} - -
+ <> + {refIds.length > 0 && ( + + {refIds.map((refId) => ( + setSelectedTab(refId)} + /> + ))} + + )} {renderContent()} -
+ ); }; @@ -225,21 +212,6 @@ const getStyles = (theme: GrafanaTheme2) => ({ schemaInfoContainer: css({ padding: theme.spacing(1), }), - schemaInspector: css({ - height: '100%', - display: 'flex', - flexDirection: 'column', - }), - // Unfortunate hack to get the close button to align with the tabs since we need to - // override the default styles of the TabsBar component. - tabsBarWrapper: css({ - flexShrink: 0, - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - width: '100%', - padding: `0 ${theme.spacing(1)}`, - }), tableCell: css({ fontSize: theme.typography.bodySmall.fontSize, fontWeight: theme.typography.fontWeightMedium, @@ -247,10 +219,8 @@ const getStyles = (theme: GrafanaTheme2) => ({ }), tableContainer: css({ margin: theme.spacing(1), - flex: 1, overflowY: 'auto', overflowX: 'auto', - minHeight: 0, // Allow flex child to shrink border: `1px solid ${theme.colors.border.medium}`, borderRadius: theme.shape.radius.default, backgroundColor: theme.colors.background.primary, diff --git a/public/app/features/expressions/components/SqlExpressions/SqlExpr.test.tsx b/public/app/features/expressions/components/SqlExpressions/SqlExpr.test.tsx index b7acc586447..9e2cfd50b75 100644 --- a/public/app/features/expressions/components/SqlExpressions/SqlExpr.test.tsx +++ b/public/app/features/expressions/components/SqlExpressions/SqlExpr.test.tsx @@ -1,4 +1,4 @@ -import { render, waitFor, fireEvent, act, testWithFeatureToggles } from 'test/test-utils'; +import { act, fireEvent, render, testWithFeatureToggles } from 'test/test-utils'; import { ExpressionQuery, ExpressionQueryType } from '../../types'; @@ -127,78 +127,6 @@ describe('SqlExpr with GenAI features', () => { queries: [], }; - it('renders GenAI buttons with empty expression', async () => { - const customProps = { ...defaultProps, query: { ...defaultProps.query, expression: '' } }; - const { findByText } = render(); - expect(await findByText('Generate suggestion')).toBeInTheDocument(); - expect(await findByText('Explain query')).toBeInTheDocument(); - }); - - it('renders GenAI buttons with non-empty expression', async () => { - const { findByText } = render(); - expect(await findByText('Improve query')).toBeInTheDocument(); - expect(await findByText('Explain query')).toBeInTheDocument(); - }); - - it('renders "Improve query" when currentQuery differs from initialQuery', async () => { - const customProps = { - ...defaultProps, - query: { ...defaultProps.query, expression: 'SELECT * FROM A WHERE value > 10' }, - }; - const { findByText } = render(); - expect(await findByText('Improve query')).toBeInTheDocument(); - }); - - it('renders View explanation button when shouldShowViewExplanation is true', async () => { - const { useSQLExplanations } = require('./GenAI/hooks/useSQLExplanations'); - useSQLExplanations.mockImplementation((currentExpression: string) => ({ - shouldShowViewExplanation: true, - })); - - const { findByText } = render(); - expect(await findByText('View explanation')).toBeInTheDocument(); - }); - - it('renders Explain query button when shouldShowViewExplanation is false', async () => { - const { useSQLExplanations } = require('./GenAI/hooks/useSQLExplanations'); - useSQLExplanations.mockImplementation((currentExpression: string) => ({ - shouldShowViewExplanation: false, - })); - - const { findByText } = render(); - expect(await findByText('Explain query')).toBeInTheDocument(); - }); - - it('renders SuggestionsDrawerButton when there are suggestions', async () => { - const { useSQLSuggestions } = require('./GenAI/hooks/useSQLSuggestions'); - useSQLSuggestions.mockImplementation(() => ({ suggestions: ['suggestion1', 'suggestion2'] })); - - const { findByTestId } = render(); - expect(await findByTestId('suggestions-badge')).toBeInTheDocument(); - }); - - it('does not render SuggestionsDrawerButton when there are no suggestions', async () => { - const { useSQLSuggestions } = require('./GenAI/hooks/useSQLSuggestions'); - useSQLSuggestions.mockImplementation(() => ({ suggestions: [] })); - - const { queryByTestId } = render(); - expect(await waitFor(() => queryByTestId('suggestions-badge'))).not.toBeInTheDocument(); - }); - - it('calls handleOpenExplanation when View explanation is clicked', async () => { - const { useSQLExplanations } = require('./GenAI/hooks/useSQLExplanations'); - const mockHandleOpen = jest.fn(); - useSQLExplanations.mockImplementation(() => ({ - shouldShowViewExplanation: true, - handleOpenExplanation: mockHandleOpen, - })); - - const { findByText } = render(); - const button = await findByText('View explanation'); - fireEvent.click(button); - expect(mockHandleOpen).toHaveBeenCalled(); - }); - it('renders suggestions drawer when isDrawerOpen is true', async () => { const { useSQLSuggestions } = require('./GenAI/hooks/useSQLSuggestions'); useSQLSuggestions.mockImplementation(() => ({ @@ -245,26 +173,26 @@ describe('Schema Inspector feature toggle', () => { }); it('closes panel and shows reopen button when close button clicked', async () => { - const { queryByText, getByLabelText, findByText } = render(); + const { queryByText, getByText, findByText } = render(); expect(queryByText('No schema information available')).toBeInTheDocument(); - const closeButton = getByLabelText('Close schema inspector'); + const closeButton = getByText('Schema inspector'); await act(async () => fireEvent.click(closeButton)); expect(queryByText('No schema information available')).not.toBeInTheDocument(); - expect(await findByText('Inspect schema')).toBeInTheDocument(); + expect(await findByText('Schema inspector')).toBeInTheDocument(); }); - it('reopens panel when inspect schema button clicked after closing', async () => { - const { queryByText, getByLabelText, getByText } = render(); + it('reopens panel when Open schema inspector button clicked after closing', async () => { + const { queryByText, getByText } = render(); - const closeButton = getByLabelText('Close schema inspector'); + const closeButton = getByText('Schema inspector'); await act(async () => fireEvent.click(closeButton)); expect(queryByText('No schema information available')).not.toBeInTheDocument(); - const reopenButton = getByText('Inspect schema'); + const reopenButton = getByText('Schema inspector'); await act(async () => fireEvent.click(reopenButton)); expect(queryByText('No schema information available')).toBeInTheDocument(); @@ -300,7 +228,7 @@ describe('Schema Inspector feature toggle', () => { it('does not render panel or button', () => { const { queryByText } = render(); - expect(queryByText('Inspect schema')).not.toBeInTheDocument(); + expect(queryByText('Schema inspector')).not.toBeInTheDocument(); expect(queryByText('No schema information available')).not.toBeInTheDocument(); }); }); diff --git a/public/app/features/expressions/components/SqlExpressions/SqlExpr.tsx b/public/app/features/expressions/components/SqlExpressions/SqlExpr.tsx index 361d748b716..a59b46b232b 100644 --- a/public/app/features/expressions/components/SqlExpressions/SqlExpr.tsx +++ b/public/app/features/expressions/components/SqlExpressions/SqlExpr.tsx @@ -1,15 +1,15 @@ import { css, cx } from '@emotion/css'; -import { useMemo, useRef, useEffect, useState, lazy, Suspense, useCallback } from 'react'; +import { lazy, Suspense, useCallback, useEffect, useMemo, useState } from 'react'; import { useMeasure } from 'react-use'; import AutoSizer from 'react-virtualized-auto-sizer'; -import { SelectableValue, GrafanaTheme2 } from '@grafana/data'; -import { t, Trans } from '@grafana/i18n'; -import { SQLEditor, CompletionItemKind, LanguageDefinition, TableIdentifier } from '@grafana/plugin-ui'; +import { GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { Trans } from '@grafana/i18n'; +import { CompletionItemKind, LanguageDefinition, SQLEditor, TableIdentifier } from '@grafana/plugin-ui'; import { reportInteraction } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema/dist/esm/index'; import { formatSQL } from '@grafana/sql'; -import { useStyles2, Stack, Button, Modal } from '@grafana/ui'; +import { Button, Stack, useStyles2 } from '@grafana/ui'; import { ExpressionQueryEditorProps } from '../../ExpressionQueryEditor'; import { SqlExpressionQuery } from '../../types'; @@ -20,27 +20,10 @@ import { getSqlCompletionProvider } from './CompletionProvider/sqlCompletionProv import { useSQLExplanations } from './GenAI/hooks/useSQLExplanations'; import { useSQLSuggestions } from './GenAI/hooks/useSQLSuggestions'; import { SchemaInspectorPanel } from './SchemaInspector/SchemaInspectorPanel'; +import { SqlExprContextValue, SqlExprProvider } from './SqlExprContext'; +import { SqlQueryActions } from './SqlQueryActions'; import { useSQLSchemas } from './hooks/useSQLSchemas'; -// Lazy load the GenAI components to avoid circular dependencies -const GenAISQLSuggestionsButton = lazy(() => - import('./GenAI/GenAISQLSuggestionsButton').then((module) => ({ - default: module.GenAISQLSuggestionsButton, - })) -); - -const GenAISQLExplainButton = lazy(() => - import('./GenAI/GenAISQLExplainButton').then((module) => ({ - default: module.GenAISQLExplainButton, - })) -); - -const SuggestionsDrawerButton = lazy(() => - import('./GenAI/SuggestionsDrawerButton').then((module) => ({ - default: module.SuggestionsDrawerButton, - })) -); - const GenAISuggestionsDrawer = lazy(() => import('./GenAI/GenAISuggestionsDrawer').then((module) => ({ default: module.GenAISuggestionsDrawer, @@ -55,7 +38,6 @@ const GenAIExplanationDrawer = lazy(() => // Account for Monaco editor's border to prevent clipping const EDITOR_BORDER_ADJUSTMENT = 2; // 1px border on top and bottom -const EDITOR_HEIGHT = 300; export interface SqlExprProps { refIds: Array>; @@ -93,14 +75,10 @@ FROM LIMIT 10`; - const [dimensions, setDimensions] = useState({ height: 0 }); - const styles = useStyles2((theme) => getStyles(theme, dimensions.height || EDITOR_HEIGHT)); - const containerRef = useRef(null); const [toolboxRef, toolboxMeasure] = useMeasure(); - const [isExpanded, setIsExpanded] = useState(false); const [isSchemaInspectorOpen, setIsSchemaInspectorOpen] = useState(true); - - const { handleApplySuggestion, handleHistoryUpdate, handleCloseDrawer, handleOpenDrawer, isDrawerOpen, suggestions } = + const styles = useStyles2((theme) => getStyles(theme)); + const { handleApplySuggestion, handleCloseDrawer, handleHistoryUpdate, handleOpenDrawer, isDrawerOpen, suggestions } = useSQLSuggestions(); const { @@ -195,21 +173,6 @@ LIMIT } }, [onRunQuery, refetchSchemas, isSchemaInspectorOpen]); - // Set up resize observer to handle container resizing - useEffect(() => { - if (!containerRef.current) { - return; - } - - const resizeObserver = new ResizeObserver((entries) => { - const { height } = entries[0].contentRect; - setDimensions({ height }); - }); - - resizeObserver.observe(containerRef.current); - return () => resizeObserver.disconnect(); - }, []); - useEffect(() => { // Call the onChange method once so we have access to the initial query in consuming components // But only if expression is empty @@ -236,168 +199,122 @@ LIMIT return () => document.removeEventListener('keydown', handleKeyDown, true); }, [executeQuery]); - const renderToolbox = (formatQuery: () => void) => ( -
- -
- ); + const contextValue: SqlExprContextValue = { + // Explanations + explanation, + isExplanationOpen, + shouldShowViewExplanation, + handleExplain, + handleOpenExplanation, + handleCloseExplanation, + // Suggestions + suggestions, + isDrawerOpen, + handleHistoryUpdate, + handleApplySuggestion, + handleOpenDrawer, + handleCloseDrawer, + }; - const renderSQLButtons = () => ( -
- - {isSchemasFeatureEnabled && !isSchemaInspectorOpen && ( - - )} - - - {shouldShowViewExplanation ? ( - - ) : ( - - )} - - - {}} // Noop - history is managed via onHistoryUpdate - onHistoryUpdate={handleHistoryUpdate} - queryContext={queryContext} - refIds={vars} - errorContext={errorContext} // Will be added when error tracking is implemented - // schemas={schemas} // Will be added when schema extraction is implemented - /> - - - {suggestions.length > 0 && ( - - - - )} -
- ); - - const renderSQLEditor = (width?: number, height?: number) => ( - <> -
- {renderSQLButtons()} -
( + + + {isSchemasFeatureEnabled && ( + + )} + + ); + + const renderMainContent = () => ( +
+
+ + {({ width, height }) => ( - {({ formatQuery }) => renderToolbox(formatQuery)} + {({ formatQuery }) => ( +
+ +
+ )}
-
- {isSchemaInspectorOpen && isSchemasFeatureEnabled && ( -
- setIsSchemaInspectorOpen(false)} - /> -
)} -
+
- - - - - - - + {isSchemaInspectorOpen && isSchemasFeatureEnabled && ( +
+ +
+ )} +
); - const renderStandaloneEditor = () => ( - - {({ width, height }) => ( - - {({ formatQuery }) => renderToolbox(formatQuery)} - - )} - + const renderSQLEditor = () => ( + + {renderButtons()} + {renderMainContent()} + ); return ( - <> - {renderSQLEditor()} - {isExpanded && ( - setIsExpanded(false)} - > - {renderStandaloneEditor()} - - )} - + +
+ {renderSQLEditor()} + + + + + + +
+
); }; -const getStyles = (theme: GrafanaTheme2, editorHeight: number) => ({ - sqlContainer: css({ - display: 'grid', - gap: theme.spacing(1), - gridTemplateRows: 'auto 1fr', - gridTemplateAreas: ` - "buttons" - "content" - `, +const getStyles = (theme: GrafanaTheme2) => ({ + mainContainer: css({ + marginTop: theme.spacing(0.5), }), - contentContainer: css({ - gridArea: 'content', + minHeight: '250px', + height: '100%', + resize: 'vertical', + overflow: 'hidden', + display: 'grid', - gap: theme.spacing(1), gridTemplateColumns: '1fr 0fr', gridTemplateAreas: '"editor schema"', [theme.transitions.handleMotion('no-preference')]: { @@ -408,67 +325,22 @@ const getStyles = (theme: GrafanaTheme2, editorHeight: number) => ({ }), contentContainerWithSchema: css({ gridTemplateColumns: '1fr 1fr', + gap: theme.spacing(1), }), editorContainer: css({ gridArea: 'editor', - height: editorHeight, // Use dynamic height from ResizeObserver - resize: 'vertical', - overflow: 'auto', - minHeight: '100px', - }), - modal: css({ - width: '95vw', - height: '95vh', - }), - modalContent: css({ height: '100%', - paddingTop: 0, - }), - // This is NOT ideal. The alternative is to expose SQL buttons as a separate component, - // Then consume them in ExpressionQueryEditor. This requires a lot of refactoring and - // can be prioritized later. - sqlButtons: css({ - gridArea: 'buttons', - justifySelf: 'end', - transform: `translateY(${theme.spacing(-4)})`, - marginBottom: theme.spacing(-4), // Prevent affecting editor position - zIndex: 10, // Ensure buttons appear above other elements - position: 'relative', // Required for z-index to work - display: 'flex', - alignItems: 'center', - gap: theme.spacing(1), + width: '100%', + overflow: 'auto', }), schemaInspector: css({ gridArea: 'schema', - height: editorHeight, + height: '100%', overflow: 'hidden', minWidth: 0, - }), - schemaInspectorOpen: css({ border: `1px solid ${theme.colors.border.weak}`, borderRadius: theme.shape.radius.default, }), - schemaFields: css({ - display: 'flex', - flexWrap: 'wrap', - gap: theme.spacing(1), - padding: theme.spacing(1), - maxHeight: '120px', - overflowY: 'auto', - }), - fieldItem: css({ - display: 'flex', - alignItems: 'center', - gap: theme.spacing(0.5), - padding: theme.spacing(1), - backgroundColor: theme.colors.background.secondary, - borderRadius: theme.shape.radius.default, - border: `1px solid ${theme.colors.border.weak}`, - fontSize: theme.typography.bodySmall.fontSize, - }), - responseContainer: css({ - padding: theme.spacing(2), - }), }); async function fetchFields(identifier: TableIdentifier, queries: DataQuery[]) { diff --git a/public/app/features/expressions/components/SqlExpressions/SqlExprContext.test.tsx b/public/app/features/expressions/components/SqlExpressions/SqlExprContext.test.tsx new file mode 100644 index 00000000000..f95128b5af6 --- /dev/null +++ b/public/app/features/expressions/components/SqlExpressions/SqlExprContext.test.tsx @@ -0,0 +1,88 @@ +import { render, screen } from 'test/test-utils'; + +import { SqlExprContextValue, SqlExprProvider, useSqlExprContext } from './SqlExprContext'; + +describe('SqlExprContext', () => { + const mockContextValue: SqlExprContextValue = { + explanation: 'Test explanation', + isExplanationOpen: false, + shouldShowViewExplanation: false, + handleExplain: jest.fn(), + handleOpenExplanation: jest.fn(), + handleCloseExplanation: jest.fn(), + suggestions: ['suggestion1', 'suggestion2'], + isDrawerOpen: false, + handleHistoryUpdate: jest.fn(), + handleApplySuggestion: jest.fn(), + handleOpenDrawer: jest.fn(), + handleCloseDrawer: jest.fn(), + }; + + describe('SqlExprProvider', () => { + it('renders children correctly', () => { + render( + +
Test Child
+
+ ); + + expect(screen.getByText('Test Child')).toBeInTheDocument(); + }); + + it('provides context value to children', () => { + const TestConsumer = () => { + const context = useSqlExprContext(); + return
{context.explanation}
; + }; + + render( + + + + ); + + expect(screen.getByText('Test explanation')).toBeInTheDocument(); + }); + }); + + describe('useSqlExprContext', () => { + it('throws error when used outside provider', () => { + const TestComponent = () => { + useSqlExprContext(); + return
Should not render
; + }; + + // Suppress console.error for this test + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + expect(() => { + render(); + }).toThrow('useSqlExprContext must be used within SqlExprProvider'); + + consoleSpy.mockRestore(); + }); + + it('returns context value when used inside provider', () => { + const TestComponent = () => { + const context = useSqlExprContext(); + return ( +
+ Explanation: {context.explanation} + Suggestions: {context.suggestions.length} + Is Drawer Open: {context.isDrawerOpen.toString()} +
+ ); + }; + + render( + + + + ); + + expect(screen.getByText('Explanation: Test explanation')).toBeInTheDocument(); + expect(screen.getByText('Suggestions: 2')).toBeInTheDocument(); + expect(screen.getByText('Is Drawer Open: false')).toBeInTheDocument(); + }); + }); +}); diff --git a/public/app/features/expressions/components/SqlExpressions/SqlExprContext.tsx b/public/app/features/expressions/components/SqlExpressions/SqlExprContext.tsx new file mode 100644 index 00000000000..e05ccff80b6 --- /dev/null +++ b/public/app/features/expressions/components/SqlExpressions/SqlExprContext.tsx @@ -0,0 +1,38 @@ +import { createContext, useContext, ReactNode } from 'react'; + +export interface SqlExprContextValue { + // Explanations + explanation: string; + isExplanationOpen: boolean; + shouldShowViewExplanation: boolean; + handleExplain: (explanation: string) => void; + handleOpenExplanation: () => void; + handleCloseExplanation: () => void; + + // Suggestions + suggestions: string[]; + isDrawerOpen: boolean; + handleHistoryUpdate: (suggestions: string[]) => void; + handleApplySuggestion: (suggestion: string) => string; + handleOpenDrawer: () => void; + handleCloseDrawer: () => void; +} + +const SqlExprContext = createContext(null); + +export const useSqlExprContext = () => { + const context = useContext(SqlExprContext); + if (!context) { + throw new Error('useSqlExprContext must be used within SqlExprProvider'); + } + return context; +}; + +interface SqlExprProviderProps { + children: ReactNode; + value: SqlExprContextValue; +} + +export const SqlExprProvider = ({ children, value }: SqlExprProviderProps) => { + return {children}; +}; diff --git a/public/app/features/expressions/components/SqlExpressions/SqlQueryActions.test.tsx b/public/app/features/expressions/components/SqlExpressions/SqlQueryActions.test.tsx new file mode 100644 index 00000000000..93e3285ed83 --- /dev/null +++ b/public/app/features/expressions/components/SqlExpressions/SqlQueryActions.test.tsx @@ -0,0 +1,137 @@ +import { fireEvent, render, waitFor } from 'test/test-utils'; + +import { SqlExprContextValue } from './SqlExprContext'; +import { SqlQueryActions, SqlQueryActionsProps } from './SqlQueryActions'; + +jest.mock('@grafana/ui', () => ({ + ...jest.requireActual('@grafana/ui'), + useStyles2: jest.fn().mockImplementation(() => ({})), +})); + +// Mock lazy loaded GenAI components +jest.mock('./GenAI/GenAISQLSuggestionsButton', () => ({ + GenAISQLSuggestionsButton: ({ currentQuery, initialQuery }: { currentQuery: string; initialQuery: string }) => { + const text = !currentQuery || currentQuery === initialQuery ? 'Generate suggestion' : 'Improve query'; + return
{text}
; + }, +})); + +jest.mock('./GenAI/GenAISQLExplainButton', () => ({ + GenAISQLExplainButton: () =>
Explain query
, +})); + +jest.mock('./GenAI/SuggestionsDrawerButton', () => ({ + SuggestionsDrawerButton: () =>
Suggestions Badge
, +})); + +// Mock SqlExprContext +const mockContextValue: SqlExprContextValue = { + handleOpenExplanation: jest.fn(), + shouldShowViewExplanation: false, + handleExplain: jest.fn(), + handleHistoryUpdate: jest.fn(), + handleOpenDrawer: jest.fn(), + suggestions: [], + explanation: '', + isExplanationOpen: false, + isDrawerOpen: false, + handleApplySuggestion: jest.fn(), + handleCloseDrawer: jest.fn(), + handleCloseExplanation: jest.fn(), +}; + +jest.mock('./SqlExprContext', () => ({ + useSqlExprContext: () => mockContextValue, + SqlExprProvider: ({ children }: { children: React.ReactNode }) => <>{children}, +})); + +describe('SqlQueryActions', () => { + const defaultProps: SqlQueryActionsProps = { + executeQuery: jest.fn(), + currentQuery: `SELECT * FROM A LIMIT 10`, + queryContext: {}, + refIds: ['A'], + initialQuery: `SELECT * FROM A LIMIT 10`, + errorContext: [], + }; + + beforeEach(() => { + jest.clearAllMocks(); + // Reset mock context to default values + Object.assign(mockContextValue, { + handleOpenExplanation: jest.fn(), + shouldShowViewExplanation: false, + handleExplain: jest.fn(), + handleHistoryUpdate: jest.fn(), + handleOpenDrawer: jest.fn(), + suggestions: [], + explanation: '', + isExplanationOpen: false, + isDrawerOpen: false, + handleApplySuggestion: jest.fn(), + handleCloseDrawer: jest.fn(), + handleCloseExplanation: jest.fn(), + }); + }); + + it('renders GenAI buttons with empty expression', async () => { + const customProps = { ...defaultProps, currentQuery: '' }; + const { findByText } = render(); + expect(await findByText('Generate suggestion')).toBeInTheDocument(); + expect(await findByText('Explain query')).toBeInTheDocument(); + }); + + it('renders GenAI buttons with non-empty expression', async () => { + const { findByText } = render(); + expect(await findByText('Generate suggestion')).toBeInTheDocument(); + expect(await findByText('Explain query')).toBeInTheDocument(); + }); + + it('renders "Improve query" when currentQuery differs from initialQuery', async () => { + const customProps = { + ...defaultProps, + currentQuery: 'SELECT * FROM A WHERE value > 10', + }; + const { findByText } = render(); + expect(await findByText('Improve query')).toBeInTheDocument(); + }); + + it('renders View explanation button when shouldShowViewExplanation is true', async () => { + mockContextValue.shouldShowViewExplanation = true; + + const { findByText } = render(); + expect(await findByText('View explanation')).toBeInTheDocument(); + }); + + it('renders Explain query button when shouldShowViewExplanation is false', async () => { + mockContextValue.shouldShowViewExplanation = false; + + const { findByText } = render(); + expect(await findByText('Explain query')).toBeInTheDocument(); + }); + + it('renders SuggestionsDrawerButton when there are suggestions', async () => { + mockContextValue.suggestions = ['suggestion1', 'suggestion2']; + + const { findByTestId } = render(); + expect(await findByTestId('suggestions-badge')).toBeInTheDocument(); + }); + + it('does not render SuggestionsDrawerButton when there are no suggestions', async () => { + mockContextValue.suggestions = []; + + const { queryByTestId } = render(); + expect(await waitFor(() => queryByTestId('suggestions-badge'))).not.toBeInTheDocument(); + }); + + it('calls handleOpenExplanation when View explanation is clicked', async () => { + const mockHandleOpen = jest.fn(); + mockContextValue.shouldShowViewExplanation = true; + mockContextValue.handleOpenExplanation = mockHandleOpen; + + const { findByText } = render(); + const button = await findByText('View explanation'); + fireEvent.click(button); + expect(mockHandleOpen).toHaveBeenCalled(); + }); +}); diff --git a/public/app/features/expressions/components/SqlExpressions/SqlQueryActions.tsx b/public/app/features/expressions/components/SqlExpressions/SqlQueryActions.tsx new file mode 100644 index 00000000000..a96a82a4e72 --- /dev/null +++ b/public/app/features/expressions/components/SqlExpressions/SqlQueryActions.tsx @@ -0,0 +1,91 @@ +import { lazy, Suspense } from 'react'; + +import { t, Trans } from '@grafana/i18n'; +import { Button, Stack } from '@grafana/ui'; + +import { useSqlExprContext } from './SqlExprContext'; + +// Lazy load the GenAI components to avoid circular dependencies +const GenAISQLSuggestionsButton = lazy(() => + import('./GenAI/GenAISQLSuggestionsButton').then((module) => ({ + default: module.GenAISQLSuggestionsButton, + })) +); + +const GenAISQLExplainButton = lazy(() => + import('./GenAI/GenAISQLExplainButton').then((module) => ({ + default: module.GenAISQLExplainButton, + })) +); + +const SuggestionsDrawerButton = lazy(() => + import('./GenAI/SuggestionsDrawerButton').then((module) => ({ + default: module.SuggestionsDrawerButton, + })) +); + +export interface SqlQueryActionsProps { + executeQuery: () => void; + currentQuery: string; + queryContext: Record; + refIds: string[]; + initialQuery: string; + errorContext: string[]; +} + +export const SqlQueryActions = ({ + executeQuery, + currentQuery, + queryContext, + refIds, + initialQuery, + errorContext, +}: SqlQueryActionsProps) => { + const { + handleOpenExplanation, + shouldShowViewExplanation, + handleExplain, + handleHistoryUpdate, + handleOpenDrawer, + suggestions, + } = useSqlExprContext(); + return ( + + + + {shouldShowViewExplanation ? ( + + ) : ( + + )} + + + {}} // Noop - history is managed via onHistoryUpdate + onHistoryUpdate={handleHistoryUpdate} + queryContext={queryContext} + refIds={refIds} + errorContext={errorContext} // Will be added when error tracking is implemented + // schemas={schemas} // Will be added when schema extraction is implemented + /> + + {suggestions.length > 0 && ( + + + + )} + + ); +}; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index d97ce1128ba..807d6fbfa4b 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -7872,23 +7872,18 @@ "label-upsample": "Upsample", "tooltip-s-m-h": "10s, 1m, 30m, 1h" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "Close schema inspector" - }, "sql-expr": { "button-run-query": "Run query", - "modal-title": "SQL Editor", "tooltip-experimental": "SQL Expressions LLM integration is experimental. Please report any issues to the Grafana team." }, "sql-schema": { - "close-schema-inspector": "Close schema inspector", "error-title": "Error", - "inspect-button": "Inspect schema", "loading": "Loading schema information...", "no-data-title": "No schema information available", "no-fields-desc": "This query returned no schema information.", "no-fields-title": "No schema information", - "query-error-title": "Query error" + "query-error-title": "Query error", + "schema-inspector": "Schema inspector" }, "threshold": { "label-input": "Input" From 5585595c16f633a2a1e6ac8efefc6f7ff0e22013 Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:41:06 +0000 Subject: [PATCH 275/297] I18n: Download translations from Crowdin (#115604) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 135 +++++++++++++++++----------- public/locales/de-DE/grafana.json | 135 +++++++++++++++++----------- public/locales/es-ES/grafana.json | 135 +++++++++++++++++----------- public/locales/fr-FR/grafana.json | 135 +++++++++++++++++----------- public/locales/hu-HU/grafana.json | 135 +++++++++++++++++----------- public/locales/id-ID/grafana.json | 135 +++++++++++++++++----------- public/locales/it-IT/grafana.json | 135 +++++++++++++++++----------- public/locales/ja-JP/grafana.json | 135 +++++++++++++++++----------- public/locales/ko-KR/grafana.json | 135 +++++++++++++++++----------- public/locales/nl-NL/grafana.json | 135 +++++++++++++++++----------- public/locales/pl-PL/grafana.json | 135 +++++++++++++++++----------- public/locales/pt-BR/grafana.json | 135 +++++++++++++++++----------- public/locales/pt-PT/grafana.json | 135 +++++++++++++++++----------- public/locales/ru-RU/grafana.json | 135 +++++++++++++++++----------- public/locales/sv-SE/grafana.json | 135 +++++++++++++++++----------- public/locales/tr-TR/grafana.json | 135 +++++++++++++++++----------- public/locales/zh-Hans/grafana.json | 135 +++++++++++++++++----------- public/locales/zh-Hant/grafana.json | 135 +++++++++++++++++----------- 18 files changed, 1476 insertions(+), 954 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 575bba19ca1..84cc597980b 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -2688,6 +2688,35 @@ "text-federated": "Federované", "text-provisioned": "Zajištěno" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "Zdroj dat", @@ -3749,6 +3778,7 @@ "text": "Nebyly nalezeny žádné výsledky pro váš dotaz" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6222,7 +6252,7 @@ "title-someone-else-has-updated-this-dashboard": "Tuto nástěnku aktualizoval jiný uživatel", "would-still-dashboard": "Chcete přesto tuto nástěnku uložit?" }, - "save-and-overwrite": "Uložit a přepsat" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7211,6 +7241,9 @@ "time-range-label": "Zamknout časový rozsah" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "Pro tip: {{proTip}}" }, @@ -7691,39 +7724,6 @@ }, "share-span": "Sdílet" }, - "span-filters": { - "aria-label-select-max-span-operator": "Vyberte operátor max. rozsahu", - "aria-label-select-min-span-operator": "Vyberte operátor min. rozsahu", - "aria-label-select-service-name": "Vyberte název služby", - "aria-label-select-service-name-operator": "Vyberte operátor názvu služby", - "aria-label-select-span-name": "Vyberte název rozsahu", - "aria-label-select-span-name-operator": "Vyberte operátor názvu rozsahu", - "ariaLabel-select-max-span-duration": "Vyberte maximální dobu trvání", - "ariaLabel-select-min-span-duration": "Vyberte minimální dobu trvání rozsahu", - "label-collapse": "Filtry rozsahu", - "label-duration": "Doba trvání", - "label-service-name": "Název služby", - "label-span-name": "Název rozsahu", - "label-tags": "Tagy", - "placeholder-all-service-names": "Všechny názvy služby", - "placeholder-all-span-names": "Všechny názvy rozsahu", - "tooltip-collapse": "Filtrujte svá rozpětí níže. Filtry můžete používat tak dlouho, dokud nezúžíte výsledná rozpětí na několik vybraných, které vás nejvíce zajímají.", - "tooltip-duration": "Filtrovat podle doby trvání. Akceptované jednotky jsou {{units}}", - "tooltip-tags": "Filtrujte podle tagů, tagů procesů nebo polí protokolu ve vybraném rozpětí." - }, - "span-filters-tags": { - "aria-label-add-tag": "Přidat tag", - "aria-label-input-tag-value": "Vstupní hodnota tagu", - "aria-label-remove-tag": "Odebrat tag", - "aria-label-select-tag-key": "Vyberte klíč tagu", - "aria-label-select-tag-operator": "Vyberte operátor tagu", - "aria-label-select-tag-value": "Vyberte hodnotu tagu", - "placeholder-select-tag": "Vyberte tag", - "placeholder-select-value": "Vyberte hodnotu", - "placeholder-tag-value": "Hodnota tagu", - "tooltip-add-tag": "Přidat tag", - "tooltip-remove-tag": "Odebrat tag" - }, "span-flame-graph": { "flame-graph": "Graf plamene" }, @@ -7922,23 +7922,18 @@ "label-upsample": "Zvýšit vzorkovací frekvenci", "tooltip-s-m-h": "10s, 1m, 30m, 1h" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "Spustit dotaz", - "modal-title": "Editor SQL", "tooltip-experimental": "Integrace LLM pro výrazy jazyka SQL je experimentální. Jakékoli problémy nahlaste týmu Grafana." }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "Vstup" @@ -8013,11 +8008,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9463,6 +9454,7 @@ "name-unit": "Jednotka", "name-value-name": "Název hodnoty", "name-y-axis-scale": "Měřítko osy Y", + "name-y-bucket-scale": "", "placeholder-axis-label": "Auto", "placeholder-axis-width": "Auto", "placeholder-decimals": "Auto", @@ -9492,6 +9484,18 @@ "label-all": "Vše", "label-hidden": "Skryté", "label-single": "Jednorázový" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11295,9 +11299,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -12074,7 +12083,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "Zobrazit podrobnosti", "loading-finished-job": "Načítání dokončené úlohy…", @@ -12180,6 +12192,9 @@ "webhook-last-event": "Poslední událost:", "webhook-url": "Zobrazit webhook" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "Zpět na úložiště", "cleaning-up-resources": "Čištění zdrojů úložiště", @@ -12284,6 +12299,9 @@ "tooltip-unhealthy-repository": "Nelze stáhnout nezdravé úložiště" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12303,10 +12321,15 @@ }, "warning-title-default": "Varování", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "Doba trvání tohoto procesu závisí na počtu zapojených zdrojů.", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12325,6 +12348,7 @@ "step-finish": "Vybrat další nastavení", "step-synchronize": "Synchronizovat s externím úložištěm", "sync-description": "Synchronizujte zdroje s externím úložištěm. Po tomto jednorázovém kroku budou všechny budoucí aktualizace automaticky uloženy do úložiště a zajištěny zpět do instance.", + "sync-option-migrate-resources": "", "title-bootstrap": "Vyberte, co chcete synchronizovat", "title-connect": "Připojit k externímu úložišti", "title-finish": "Vybrat další nastavení", @@ -12564,16 +12588,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 9df0dd8a2da..ef5f649123d 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -2666,6 +2666,35 @@ "text-federated": "Verbunden", "text-provisioned": "Bereitgestellt" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "Datenquelle", @@ -3717,6 +3746,7 @@ "text": "Keine Ergebnisse für deine Abfrage gefunden" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6180,7 +6210,7 @@ "title-someone-else-has-updated-this-dashboard": "Eine andere Person hat dieses Dashboard aktualisiert", "would-still-dashboard": "Möchten Sie dieses Dashboard trotzdem speichern?" }, - "save-and-overwrite": "'Speichern und überschreiben'" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7165,6 +7195,9 @@ "time-range-label": "Zeitbereich sperren" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "Profitipp: {{proTip}}" }, @@ -7641,39 +7674,6 @@ }, "share-span": "Teilen" }, - "span-filters": { - "aria-label-select-max-span-operator": "Höchstspannen-Operator auswählen", - "aria-label-select-min-span-operator": "Mindestspannen-Operator auswählen", - "aria-label-select-service-name": "Dienstname auswählen", - "aria-label-select-service-name-operator": "Dienstnamen-Operator auswählen", - "aria-label-select-span-name": "Spannen-Name auswählen", - "aria-label-select-span-name-operator": "Spannen-Namen-Operator auswählen", - "ariaLabel-select-max-span-duration": "Dauer der max. Spanne auswählen", - "ariaLabel-select-min-span-duration": "Dauer der min. Spanne auswählen", - "label-collapse": "Spannenfilter", - "label-duration": "Dauer", - "label-service-name": "Dienstname", - "label-span-name": "Spannen-Name", - "label-tags": "Tags", - "placeholder-all-service-names": "Alle Dienstnamen", - "placeholder-all-span-names": "Alle Spannen-Namen", - "tooltip-collapse": "Filtern Sie unten Ihre Spannen. Sie können weiterhin Filter anwenden, bis Sie Ihre resultierenden Spannen auf die wenigen eingegrenzt haben, die für Sie am meisten von Interesse sind.", - "tooltip-duration": "Nach Dauer filtern. Zulässige Einheiten sind {{units}}", - "tooltip-tags": "Filtern Sie in Ihren Spannen nach Tags, Prozess-Tags oder Log-Feldern." - }, - "span-filters-tags": { - "aria-label-add-tag": "Tag hinzufügen", - "aria-label-input-tag-value": "Tag-Wert eingeben", - "aria-label-remove-tag": "Tag entfernen", - "aria-label-select-tag-key": "Tag-Key auswählen", - "aria-label-select-tag-operator": "Tag-Operator auswählen", - "aria-label-select-tag-value": "Tag-Wert auswählen", - "placeholder-select-tag": "Tag auswählen", - "placeholder-select-value": "Wert auswählen", - "placeholder-tag-value": "Tag-Wert", - "tooltip-add-tag": "Tag hinzufügen", - "tooltip-remove-tag": "Tag entfernen" - }, "span-flame-graph": { "flame-graph": "Flammendiagramm" }, @@ -7872,23 +7872,18 @@ "label-upsample": "Upsample", "tooltip-s-m-h": "10 s, 1 min., 30 min., 1 h" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "Abfrage ausführen", - "modal-title": "SQL Editor", "tooltip-experimental": "Die Integration des SQL Expressions LLM ist experimentell. Bitte melden Sie Probleme dem Grafana-Team." }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "Eingabe" @@ -7963,11 +7958,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9393,6 +9384,7 @@ "name-unit": "Einheit", "name-value-name": "Wertname", "name-y-axis-scale": "Y-Achsenskala", + "name-y-bucket-scale": "", "placeholder-axis-label": "Auto", "placeholder-axis-width": "Auto", "placeholder-decimals": "Auto", @@ -9422,6 +9414,18 @@ "label-all": "Alles", "label-hidden": "Ausgeblendet", "label-single": "Einzeln" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11203,9 +11207,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -11972,7 +11981,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "Details anzeigen", "loading-finished-job": "Fertiger Auftrag wird geladen …", @@ -12078,6 +12090,9 @@ "webhook-last-event": "Letztes Ereignis:", "webhook-url": "Webhook anzeigen" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "Zurück zu den Repositorys", "cleaning-up-resources": "Bereinigen von Repository-Ressourcen", @@ -12182,6 +12197,9 @@ "tooltip-unhealthy-repository": "Ein fehlerhaftes Repository kann nicht abgerufen werden" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12201,10 +12219,15 @@ }, "warning-title-default": "Warnung", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "Die Dauer dieses Prozesses hängt von der Anzahl der betroffenen Ressourcen ab.", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12223,6 +12246,7 @@ "step-finish": "Zusätzliche Einstellungen auswählen", "step-synchronize": "Mit externem Speicher synchronisieren", "sync-description": "Synchronisieren Sie Ressourcen mit externem Speicher. Nach diesem einmaligen Schritt werden alle zukünftigen Updates automatisch im Repository gespeichert und wieder in der Instanz bereitgestellt.", + "sync-option-migrate-resources": "", "title-bootstrap": "Wählen Sie aus, was synchronisiert wird", "title-connect": "Mit externem Speicher verbinden", "title-finish": "Zusätzliche Einstellungen auswählen", @@ -12460,16 +12484,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 1c8c641cfad..443dbefbc39 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -2666,6 +2666,35 @@ "text-federated": "Federado", "text-provisioned": "Provisionado" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "Fuente de datos", @@ -3717,6 +3746,7 @@ "text": "No se han encontrado resultados para tu consulta" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6180,7 +6210,7 @@ "title-someone-else-has-updated-this-dashboard": "Otra persona ha actualizado este dashboard", "would-still-dashboard": "¿Seguro que quieres guardar este dashboard?" }, - "save-and-overwrite": "«Guardar y sobrescribir»" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7165,6 +7195,9 @@ "time-range-label": "Bloquear el intervalo de tiempo" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "Consejo profesional: {{proTip}}" }, @@ -7641,39 +7674,6 @@ }, "share-span": "Compartir" }, - "span-filters": { - "aria-label-select-max-span-operator": "Seleccionar operador de intervalo máximo", - "aria-label-select-min-span-operator": "Seleccionar operador de intervalo mínimo", - "aria-label-select-service-name": "Seleccionar nombre de servicio", - "aria-label-select-service-name-operator": "Seleccionar operador de nombre de servicio", - "aria-label-select-span-name": "Seleccionar nombre de intervalo", - "aria-label-select-span-name-operator": "Seleccionar operador de nombre de intervalo", - "ariaLabel-select-max-span-duration": "Seleccionar duración máxima del intervalo", - "ariaLabel-select-min-span-duration": "Seleccionar duración mínima del intervalo", - "label-collapse": "Filtros de intervalo", - "label-duration": "Duración", - "label-service-name": "Nombre de servicio", - "label-span-name": "Nombre del intervalo", - "label-tags": "Etiquetas", - "placeholder-all-service-names": "Todos los nombres de servicios", - "placeholder-all-span-names": "Todos los nombres de intervalo", - "tooltip-collapse": "Filtre sus intervalos a continuación. Puede seguir aplicando filtros hasta que haya reducido los intervalos resultantes a los que más le interesen.", - "tooltip-duration": "Filtra por duración. Las unidades aceptadas son {{units}}", - "tooltip-tags": "Filtra por etiquetas, etiquetas de proceso o campos de logs en tus intervalos." - }, - "span-filters-tags": { - "aria-label-add-tag": "Añadir etiqueta", - "aria-label-input-tag-value": "Introducir valor de la etiqueta", - "aria-label-remove-tag": "Quitar etiqueta", - "aria-label-select-tag-key": "Seleccionar clave de etiqueta", - "aria-label-select-tag-operator": "Seleccionar operador de etiqueta", - "aria-label-select-tag-value": "Seleccionar valor de etiqueta", - "placeholder-select-tag": "Seleccionar etiqueta", - "placeholder-select-value": "Seleccionar valor", - "placeholder-tag-value": "Valor de etiqueta", - "tooltip-add-tag": "Añadir etiqueta", - "tooltip-remove-tag": "Quitar etiqueta" - }, "span-flame-graph": { "flame-graph": "Gráfico de llama" }, @@ -7872,23 +7872,18 @@ "label-upsample": "Aumentar tamaño", "tooltip-s-m-h": "10 s, 1 m, 30 m, 1 h" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "Ejecutar consulta", - "modal-title": "Editor de SQL", "tooltip-experimental": "La integración de LLM de expresiones SQL es experimental. Avisa de cualquier problema al equipo de Grafana." }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "Entrada" @@ -7963,11 +7958,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9393,6 +9384,7 @@ "name-unit": "Unidad", "name-value-name": "Nombre del valor", "name-y-axis-scale": "Escala del eje Y", + "name-y-bucket-scale": "", "placeholder-axis-label": "Auto", "placeholder-axis-width": "Auto", "placeholder-decimals": "Auto", @@ -9422,6 +9414,18 @@ "label-all": "Todo", "label-hidden": "Oculto", "label-single": "Único" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11203,9 +11207,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -11972,7 +11981,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "Ver detalles", "loading-finished-job": "Cargando trabajo finalizado...", @@ -12078,6 +12090,9 @@ "webhook-last-event": "Último evento:", "webhook-url": "Ver webhook" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "Volver a los repositorios", "cleaning-up-resources": "Limpiando los recursos del repositorio", @@ -12182,6 +12197,9 @@ "tooltip-unhealthy-repository": "No se puede extraer un repositorio que no está en buen estado" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12201,10 +12219,15 @@ }, "warning-title-default": "Advertencia", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "La duración de este proceso depende del número de recursos involucrados.", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12223,6 +12246,7 @@ "step-finish": "Elegir ajustes adicionales", "step-synchronize": "Sincronizar con un almacenamiento externo", "sync-description": "Sincroniza los recursos con un almacenamiento externo. Después de este paso único, todas las actualizaciones futuras se guardarán automáticamente en el repositorio y se aprovisionarán de nuevo en la instancia.", + "sync-option-migrate-resources": "", "title-bootstrap": "Elegir qué sincronizar", "title-connect": "Conectar a un almacenamiento externo", "title-finish": "Elegir ajustes adicionales", @@ -12460,16 +12484,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index d2faa93f8e5..1d98e007593 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -2666,6 +2666,35 @@ "text-federated": "Fédéré", "text-provisioned": "Mis en service" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "Source de données", @@ -3717,6 +3746,7 @@ "text": "Aucun résultat n'a été trouvé pour votre requête" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6180,7 +6210,7 @@ "title-someone-else-has-updated-this-dashboard": "Quelqu’un d’autre a mis à jour ce tableau de bord", "would-still-dashboard": "Voulez-vous toujours enregistrer ce tableau de bord ?" }, - "save-and-overwrite": "« Enregistrer et écraser »" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7165,6 +7195,9 @@ "time-range-label": "Verrouiller la période temporelle" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "Conseil de pro : {{proTip}}" }, @@ -7641,39 +7674,6 @@ }, "share-span": "Partager" }, - "span-filters": { - "aria-label-select-max-span-operator": "Sélectionner l’opérateur de la durée maximale", - "aria-label-select-min-span-operator": "Sélectionner l’opérateur de la durée minimale", - "aria-label-select-service-name": "Sélectionner le nom de service", - "aria-label-select-service-name-operator": "Sélectionner l’opérateur de nom de service", - "aria-label-select-span-name": "Sélectionner le nom de la durée", - "aria-label-select-span-name-operator": "Sélectionner l’opérateur de nom de la durée", - "ariaLabel-select-max-span-duration": "Sélectionner la durée maximale de segment", - "ariaLabel-select-min-span-duration": "Sélectionner la durée minimale de segment", - "label-collapse": "Filtres de durée", - "label-duration": "Durée", - "label-service-name": "Nom du service", - "label-span-name": "Nom de la durée", - "label-tags": "Étiquettes", - "placeholder-all-service-names": "Tous les noms de service", - "placeholder-all-span-names": "Tous les noms de durée", - "tooltip-collapse": "Filtrez vos plages ci-dessous. Vous pouvez continuer à appliquer des filtres jusqu’à ce que vous ayez réduit votre plage de résultats à ceux qui vous intéressent le plus.", - "tooltip-duration": "Filtrer par durée. Les unités acceptées sont {{units}}", - "tooltip-tags": "Filtrez par balises, balises de processus ou champs de journal dans vos durées." - }, - "span-filters-tags": { - "aria-label-add-tag": "Ajouter une balise", - "aria-label-input-tag-value": "Saisir la valeur de la balise", - "aria-label-remove-tag": "Supprimer la balise", - "aria-label-select-tag-key": "Sélectionner la clé de la balise", - "aria-label-select-tag-operator": "Sélectionner l’opérateur de la balise", - "aria-label-select-tag-value": "Sélectionner la valeur de la balise", - "placeholder-select-tag": "Choisir une balise", - "placeholder-select-value": "Sélectionner une valeur", - "placeholder-tag-value": "Valeur de la balise", - "tooltip-add-tag": "Ajouter une balise", - "tooltip-remove-tag": "Supprimer la balise" - }, "span-flame-graph": { "flame-graph": "Graphique de flamme" }, @@ -7872,23 +7872,18 @@ "label-upsample": "Sur-échantillonner", "tooltip-s-m-h": "10 s, 1 m, 30 m, 1 h" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "Exécuter la requête", - "modal-title": "Éditeur SQL", "tooltip-experimental": "L’intégration des expressions SQL avec les LLM est expérimentale. Merci de signaler tout problème à l’équipe Grafana." }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "Entrée" @@ -7963,11 +7958,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9393,6 +9384,7 @@ "name-unit": "Unité", "name-value-name": "Nom de la valeur", "name-y-axis-scale": "Échelle de l’axe Y", + "name-y-bucket-scale": "", "placeholder-axis-label": "Auto", "placeholder-axis-width": "Auto", "placeholder-decimals": "Auto", @@ -9422,6 +9414,18 @@ "label-all": "Tous", "label-hidden": "Masqué", "label-single": "Unique" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11203,9 +11207,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -11972,7 +11981,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "Afficher les détails", "loading-finished-job": "Chargement de mission terminée…", @@ -12078,6 +12090,9 @@ "webhook-last-event": "Dernier événement :", "webhook-url": "Voir le webhook" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "Retour aux référentiels", "cleaning-up-resources": "Nettoyage des ressources du référentiel", @@ -12182,6 +12197,9 @@ "tooltip-unhealthy-repository": "Impossible de fusionner un référentiel en mauvais état" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12201,10 +12219,15 @@ }, "warning-title-default": "Avertissement", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "La durée de ce processus dépend du nombre de ressources impliquées.", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12223,6 +12246,7 @@ "step-finish": "Choisir des paramètres supplémentaires", "step-synchronize": "Synchroniser avec un stockage externe", "sync-description": "Synchronisez les ressources avec un stockage externe. Après cette étape unique, toutes les futures mises à jour seront automatiquement enregistrées dans le référentiel et mises en service dans l’instance.", + "sync-option-migrate-resources": "", "title-bootstrap": "Choisir ce qui doit être synchronisé", "title-connect": "Se connecter à un stockage externe", "title-finish": "Choisir des paramètres supplémentaires", @@ -12460,16 +12484,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 0c8c164742b..bfe9d3e9542 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -2666,6 +2666,35 @@ "text-federated": "Összevont", "text-provisioned": "Kiépítve" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "Adatforrás", @@ -3717,6 +3746,7 @@ "text": "Nincs találat a lekérdezésre" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6180,7 +6210,7 @@ "title-someone-else-has-updated-this-dashboard": "Valaki más frissítette ezt az irányítópultot", "would-still-dashboard": "Biztosan menti ezt az irányítópultot?" }, - "save-and-overwrite": "„Mentés és felülírás”" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7165,6 +7195,9 @@ "time-range-label": "Időtartomány zárolása" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "ProTip: {{proTip}}" }, @@ -7641,39 +7674,6 @@ }, "share-span": "Megosztás" }, - "span-filters": { - "aria-label-select-max-span-operator": "Max. terjedelem operátor kijelölése", - "aria-label-select-min-span-operator": "Válasszon operátort a min. terjedelemhez", - "aria-label-select-service-name": "Válasszon szolgáltatásnevet", - "aria-label-select-service-name-operator": "Válasszon operátort a szolgáltatásnévhez", - "aria-label-select-span-name": "Válasszon terjedelemnevet", - "aria-label-select-span-name-operator": "Terjedelemnév operátor kijelölése", - "ariaLabel-select-max-span-duration": "Maximális időtartam kiválasztása", - "ariaLabel-select-min-span-duration": "Minimális időtartam kiválasztása", - "label-collapse": "Terjedelemszűrők", - "label-duration": "Időtartam", - "label-service-name": "Szolgáltatásnév", - "label-span-name": "Terjedelemnév", - "label-tags": "Címkék", - "placeholder-all-service-names": "Összes szolgáltatásnév", - "placeholder-all-span-names": "Összes terjedelemnév", - "tooltip-collapse": "Alább szűrheti a terjedelmeket. Folytathatja a szűrők alkalmazását, amíg a kapott terjedelmeket a leginkább keresett néhány terjedelemre nem szűkíti.", - "tooltip-duration": "Szűrés időtartam szerint. Elfogadott mértékegységek: {{units}}", - "tooltip-tags": "Szűrés címkék, folyamatcímkék vagy naplómezők alapján a terjedelmeiben." - }, - "span-filters-tags": { - "aria-label-add-tag": "Címke hozzáadása", - "aria-label-input-tag-value": "Bemeneti címke értéke", - "aria-label-remove-tag": "Címke eltávolítása", - "aria-label-select-tag-key": "Címkekulcs kijelölése", - "aria-label-select-tag-operator": "Címkeoperátor kijelölése", - "aria-label-select-tag-value": "Címkeérték kijelölése", - "placeholder-select-tag": "Címke kijelölése", - "placeholder-select-value": "Érték kijelölése", - "placeholder-tag-value": "Címke értéke", - "tooltip-add-tag": "Címke hozzáadása", - "tooltip-remove-tag": "Címke eltávolítása" - }, "span-flame-graph": { "flame-graph": "Lángdiagram" }, @@ -7872,23 +7872,18 @@ "label-upsample": "Mintasűrűség növelése", "tooltip-s-m-h": "10 mp., 1 p., 30 p., 1 ó." }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "Lekérdezés futtatása", - "modal-title": "SQL-szerkesztő", "tooltip-experimental": "Az SQL-kifejezések LLM-integrációja kísérleti jellegű. Kérjük, jelentse az esetleges problémákat a Grafana csapatának." }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "Bemenet" @@ -7963,11 +7958,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9393,6 +9384,7 @@ "name-unit": "Egység", "name-value-name": "Érték neve", "name-y-axis-scale": "Y tengely skálája", + "name-y-bucket-scale": "", "placeholder-axis-label": "Automatikus", "placeholder-axis-width": "Automatikus", "placeholder-decimals": "Automatikus", @@ -9422,6 +9414,18 @@ "label-all": "Összes", "label-hidden": "Rejtett", "label-single": "Különálló" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11203,9 +11207,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -11972,7 +11981,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "Részletek megtekintése", "loading-finished-job": "Befejezett feladat betöltése…", @@ -12078,6 +12090,9 @@ "webhook-last-event": "Legutóbbi esemény:", "webhook-url": "Webkapocs megtekintése" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "Vissza az adattárakhoz", "cleaning-up-resources": "Adattári erőforrások tisztítása", @@ -12182,6 +12197,9 @@ "tooltip-unhealthy-repository": "Nem lehet beolvasni egy nem megfelelő állapotú adattárat" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12201,10 +12219,15 @@ }, "warning-title-default": "Figyelmeztetés", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "A folyamat időtartama az érintett erőforrások számától függ.", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12223,6 +12246,7 @@ "step-finish": "Válassza ki a további beállításokat", "step-synchronize": "Szinkronizálás külső tárolóval", "sync-description": "Erőforrások szinkronizálása külső tárolóval. Ezután az egyszeri lépés után az összes jövőbeli frissítés automatikusan mentve lesz az adattárba, és vissza lesz építve a példányba.", + "sync-option-migrate-resources": "", "title-bootstrap": "Válassza ki, mit szeretne szinkronizálni", "title-connect": "Csatlakozás külső tárolóhoz", "title-finish": "Válassza ki a további beállításokat", @@ -12460,16 +12484,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 66ad5574fb6..86b18767abb 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -2655,6 +2655,35 @@ "text-federated": "Federasi", "text-provisioned": "Disediakan" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "Sumber data", @@ -3701,6 +3730,7 @@ "text": "Hasil untuk kueri Anda tidak ditemukan" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6159,7 +6189,7 @@ "title-someone-else-has-updated-this-dashboard": "Orang lain telah memperbarui dasbor ini", "would-still-dashboard": "Ingin tetap menyimpan dasbor ini?" }, - "save-and-overwrite": "'Simpan dan timpa'" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7142,6 +7172,9 @@ "time-range-label": "Kunci rentang waktu" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "Kiat Pro: {{proTip}}" }, @@ -7616,39 +7649,6 @@ }, "share-span": "Bagikan" }, - "span-filters": { - "aria-label-select-max-span-operator": "Pilih operator rentang maksimum", - "aria-label-select-min-span-operator": "Pilih operator rentang min", - "aria-label-select-service-name": "Pilih nama layanan", - "aria-label-select-service-name-operator": "Pilih operator nama layanan", - "aria-label-select-span-name": "Pilih nama rentang", - "aria-label-select-span-name-operator": "Pilih operator nama rentang", - "ariaLabel-select-max-span-duration": "Pilih durasi rentang maksimum", - "ariaLabel-select-min-span-duration": "Pilih durasi rentang minimum", - "label-collapse": "Filter Rentang", - "label-duration": "Durasi", - "label-service-name": "Nama layanan", - "label-span-name": "Nama rentang", - "label-tags": "Tag", - "placeholder-all-service-names": "Semua nama layanan", - "placeholder-all-span-names": "Semua nama rentang", - "tooltip-collapse": "Filter rentang Anda di bawah ini. Anda dapat terus menerapkan filter hingga Anda mempersempit rentang hasil Anda menjadi beberapa pilihan yang paling Anda minati.", - "tooltip-duration": "Filter menurut durasi. Unit yang diterima adalah {{units}}", - "tooltip-tags": "Filter berdasarkan tag, tag proses, atau bidang log di rentang Anda." - }, - "span-filters-tags": { - "aria-label-add-tag": "Tambah tag", - "aria-label-input-tag-value": "Masukkan nilai tag", - "aria-label-remove-tag": "Hapus tag", - "aria-label-select-tag-key": "Pilih kunci tag", - "aria-label-select-tag-operator": "Pilih operator tag", - "aria-label-select-tag-value": "Pilih nilai tag", - "placeholder-select-tag": "Pilih tag", - "placeholder-select-value": "Pilih nilai", - "placeholder-tag-value": "Nilai tag", - "tooltip-add-tag": "Tambah tag", - "tooltip-remove-tag": "Hapus tag" - }, "span-flame-graph": { "flame-graph": "Grafik api" }, @@ -7847,23 +7847,18 @@ "label-upsample": "Tingkatkan sample", "tooltip-s-m-h": "10 dtk, 1 mnt, 30 mnt, 1 jm" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "Jalankan kueri", - "modal-title": "Editor SQL", "tooltip-experimental": "Integrasi LLM Ekspresi SQL bersifat eksperimental. Harap laporkan masalah apa pun kepada tim Grafana." }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "Input" @@ -7938,11 +7933,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9358,6 +9349,7 @@ "name-unit": "Unit", "name-value-name": "Nama nilai", "name-y-axis-scale": "Skala sumbu Y", + "name-y-bucket-scale": "", "placeholder-axis-label": "Otomatis", "placeholder-axis-width": "Otomatis", "placeholder-decimals": "Otomatis", @@ -9387,6 +9379,18 @@ "label-all": "Semua", "label-hidden": "Tersembunyi", "label-single": "Tunggal" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11157,9 +11161,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -11921,7 +11930,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "Lihat detail", "loading-finished-job": "Memuat pekerjaan yang sudah selesai...", @@ -12027,6 +12039,9 @@ "webhook-last-event": "Peristiwa Terakhir:", "webhook-url": "Lihat Webhook" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "Kembali ke repositori", "cleaning-up-resources": "Membersihkan sumber daya repositori", @@ -12131,6 +12146,9 @@ "tooltip-unhealthy-repository": "Tidak dapat menerapkan pull pada repositori yang tidak sehat" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12150,10 +12168,15 @@ }, "warning-title-default": "Peringatan", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "Durasi proses ini bergantung pada jumlah sumber daya yang terlibat.", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12172,6 +12195,7 @@ "step-finish": "Pilih pengaturan tambahan", "step-synchronize": "Sinkronkan dengan penyimpanan eksternal", "sync-description": "Sinkronkan sumber daya dengan penyimpanan eksternal. Setelah langkah satu kali ini, semua pembaruan mendatang akan disimpan secara otomatis ke repositori dan disediakan kembali ke instans.", + "sync-option-migrate-resources": "", "title-bootstrap": "Pilih item yang akan disinkronkan", "title-connect": "Hubungkan ke penyimpanan eksternal", "title-finish": "Pilih pengaturan tambahan", @@ -12408,16 +12432,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 76898afd674..976d81b2f37 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -2666,6 +2666,35 @@ "text-federated": "Federato", "text-provisioned": "Fornito" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "Sorgente dati", @@ -3717,6 +3746,7 @@ "text": "Nessun risultato trovato per la ricerca" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6180,7 +6210,7 @@ "title-someone-else-has-updated-this-dashboard": "Qualcun altro ha aggiornato questa dashboard", "would-still-dashboard": "Desideri comunque salvare questa dashboard?" }, - "save-and-overwrite": "\"Salva e sovrascrivi\"" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7165,6 +7195,9 @@ "time-range-label": "Blocca intervallo di tempo" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "Suggerimento pro: {{proTip}} " }, @@ -7641,39 +7674,6 @@ }, "share-span": "Condividi" }, - "span-filters": { - "aria-label-select-max-span-operator": "Seleziona l'operatore di intervallo massimo", - "aria-label-select-min-span-operator": "Seleziona operatore di intervallo minimo", - "aria-label-select-service-name": "Seleziona nome servizio", - "aria-label-select-service-name-operator": "Seleziona operatore nome servizio", - "aria-label-select-span-name": "Seleziona nome intervallo", - "aria-label-select-span-name-operator": "Seleziona l'operatore del nome dell'intervallo", - "ariaLabel-select-max-span-duration": "Seleziona la durata dell'intervallo massimo", - "ariaLabel-select-min-span-duration": "Seleziona la durata dell'intervallo minimo", - "label-collapse": "Filtri intervallo", - "label-duration": "Durata", - "label-service-name": "Nome del servizio", - "label-span-name": "Nome intervallo", - "label-tags": "Tag", - "placeholder-all-service-names": "Tutti i nomi dei servizi", - "placeholder-all-span-names": "Tutti i nomi degli intervalli", - "tooltip-collapse": "Filtra i tuoi intervalli qui sotto. Puoi continuare ad applicare i filtri fino a quando non avrai ristretto gli intervalli risultanti ai pochi selezionati a cui sei più interessato.", - "tooltip-duration": "Filtra per durata. Le unità accettate sono {{units}}", - "tooltip-tags": "Filtra per tag, tag di processo o campi di registro nei tuoi intervalli." - }, - "span-filters-tags": { - "aria-label-add-tag": "Aggiungi tag", - "aria-label-input-tag-value": "Valore del tag di inserimento", - "aria-label-remove-tag": "Rimuovi tag", - "aria-label-select-tag-key": "Seleziona chiave tag", - "aria-label-select-tag-operator": "Seleziona operatore tag", - "aria-label-select-tag-value": "Seleziona valore tag", - "placeholder-select-tag": "Seleziona tag", - "placeholder-select-value": "Seleziona valore", - "placeholder-tag-value": "Valore del tag", - "tooltip-add-tag": "Aggiungi tag", - "tooltip-remove-tag": "Rimuovi tag" - }, "span-flame-graph": { "flame-graph": "Grafico a fiamma" }, @@ -7872,23 +7872,18 @@ "label-upsample": "Aumenta campionamento", "tooltip-s-m-h": "10 s, 1 m, 30 m, 1 h" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "Esegui query", - "modal-title": "Editor SQL", "tooltip-experimental": "L'integrazione LLM delle Espressioni SQL è sperimentale. Segnala eventuali problemi al team Grafana." }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "Inserisci" @@ -7963,11 +7958,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9393,6 +9384,7 @@ "name-unit": "Unità", "name-value-name": "Nome del valore", "name-y-axis-scale": "Scala asse Y", + "name-y-bucket-scale": "", "placeholder-axis-label": "Automatico", "placeholder-axis-width": "Automatico", "placeholder-decimals": "Automatico", @@ -9422,6 +9414,18 @@ "label-all": "Tutti", "label-hidden": "Nascosto", "label-single": "Singolo" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11203,9 +11207,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -11972,7 +11981,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "Visualizza dettagli", "loading-finished-job": "Caricamento attività terminata in corso...", @@ -12078,6 +12090,9 @@ "webhook-last-event": "Ultimo evento:", "webhook-url": "Visualizza webhook" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "Torna ai repository", "cleaning-up-resources": "Pulizia delle risorse del repository", @@ -12182,6 +12197,9 @@ "tooltip-unhealthy-repository": "Impossibile eseguire il pull di un repository non integro" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12201,10 +12219,15 @@ }, "warning-title-default": "Attenzione", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "La durata di questo processo dipende dal numero di risorse coinvolte.", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12223,6 +12246,7 @@ "step-finish": "Scegli impostazioni aggiuntive", "step-synchronize": "Sincronizza con la memoria esterna", "sync-description": "Sincronizza le risorse con la memoria esterna. Dopo questo passaggio una tantum, tutti gli aggiornamenti futuri verranno salvati automaticamente nel repository e ripristinati nell'istanza.", + "sync-option-migrate-resources": "", "title-bootstrap": "Scegli cosa sincronizzare", "title-connect": "Connetti a una memoria esterna", "title-finish": "Scegli impostazioni aggiuntive", @@ -12460,16 +12484,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index cebb4cd6bad..9bfbc78a21e 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -2655,6 +2655,35 @@ "text-federated": "フェデレーション", "text-provisioned": "プロビジョニング済み" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "データソース", @@ -3701,6 +3730,7 @@ "text": "クエリに一致する結果が見つかりませんでした。" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6159,7 +6189,7 @@ "title-someone-else-has-updated-this-dashboard": "他のユーザーがこのダッシュボードを更新しました", "would-still-dashboard": "このダッシュボードの保存を続行しますか?" }, - "save-and-overwrite": "「保存して上書き」" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7142,6 +7172,9 @@ "time-range-label": "時間範囲をロック" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "プロのヒント:{{proTip}} " }, @@ -7616,39 +7649,6 @@ }, "share-span": "共有" }, - "span-filters": { - "aria-label-select-max-span-operator": "最大スパン演算子を選択", - "aria-label-select-min-span-operator": "最小スパン演算子を選択", - "aria-label-select-service-name": "サービス名を選択", - "aria-label-select-service-name-operator": "サービス名演算子を選択", - "aria-label-select-span-name": "スパン名を選択", - "aria-label-select-span-name-operator": "スパン名演算子を選択", - "ariaLabel-select-max-span-duration": "最大スパン期間を選択", - "ariaLabel-select-min-span-duration": "最小スパン期間を選択", - "label-collapse": "スパンフィルター", - "label-duration": "継続時間", - "label-service-name": "サービス名", - "label-span-name": "スパン名", - "label-tags": "タグ", - "placeholder-all-service-names": "すべてのサービス名", - "placeholder-all-span-names": "すべてのスパン名", - "tooltip-collapse": "以下のスパンを絞り込みます。最も関心のある少数のスパンに絞り込むまで、フィルターを適用し続けることができます。", - "tooltip-duration": "期間で絞り込みます。使用可能な単位:{{units}}", - "tooltip-tags": "スパン内のタグ、プロセスタグ、またはログフィールドで絞り込みます。" - }, - "span-filters-tags": { - "aria-label-add-tag": "タグを追加", - "aria-label-input-tag-value": "タグ値を入力", - "aria-label-remove-tag": "タグを削除", - "aria-label-select-tag-key": "タグキーを選択", - "aria-label-select-tag-operator": "タグ演算子を選択", - "aria-label-select-tag-value": "タグ値を選択", - "placeholder-select-tag": "タグを選択", - "placeholder-select-value": "値を選択", - "placeholder-tag-value": "タグ値", - "tooltip-add-tag": "タグを追加", - "tooltip-remove-tag": "タグを削除" - }, "span-flame-graph": { "flame-graph": "フレームグラフ" }, @@ -7847,23 +7847,18 @@ "label-upsample": "アップサンプリング", "tooltip-s-m-h": "10秒、1分、30分、1時間" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "クエリを実行", - "modal-title": "SQLエディター", "tooltip-experimental": "SQL Expressions LLMの統合は実験的です。問題が発生した場合は、Grafanaチームに報告してください。" }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "入力" @@ -7938,11 +7933,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9358,6 +9349,7 @@ "name-unit": "単位", "name-value-name": "値の名前", "name-y-axis-scale": "Y軸スケール", + "name-y-bucket-scale": "", "placeholder-axis-label": "自動", "placeholder-axis-width": "自動", "placeholder-decimals": "自動", @@ -9387,6 +9379,18 @@ "label-all": "すべて", "label-hidden": "非表示", "label-single": "単体" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11157,9 +11161,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -11921,7 +11930,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "詳細を表示", "loading-finished-job": "完了したジョブを読み込み中...", @@ -12027,6 +12039,9 @@ "webhook-last-event": "最新イベント:", "webhook-url": "Webhookを表示" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "リポジトリに戻る", "cleaning-up-resources": "リポジトリリソースのクリーンアップ中", @@ -12131,6 +12146,9 @@ "tooltip-unhealthy-repository": "問題のあるリポジトリをプルできません" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12150,10 +12168,15 @@ }, "warning-title-default": "警告", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "このプロセスの所要時間は、関連するリソースの数によって異なります。", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12172,6 +12195,7 @@ "step-finish": "追加設定を選択", "step-synchronize": "外部ストレージと同期", "sync-description": "リソースを外部ストレージと同期します。この一度限りの手順が完了すると、その後のすべての更新は自動的にリポジトリに保存され、インスタンスにプロビジョニングされます。", + "sync-option-migrate-resources": "", "title-bootstrap": "同期する内容を選択", "title-connect": "外部ストレージに接続", "title-finish": "追加設定を選択", @@ -12408,16 +12432,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 07e410e67e1..bd4103a1de1 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -2655,6 +2655,35 @@ "text-federated": "연합됨", "text-provisioned": "프로비저닝됨" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "데이터 소스", @@ -3701,6 +3730,7 @@ "text": "쿼리에 대해 찾은 결과 없음" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6159,7 +6189,7 @@ "title-someone-else-has-updated-this-dashboard": "다른 사람이 이 대시보드를 업데이트했습니다", "would-still-dashboard": "그래도 이 대시보드를 저장하시겠어요?" }, - "save-and-overwrite": "'저장 및 덮어쓰기'" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7142,6 +7172,9 @@ "time-range-label": "시간 범위 잠그기" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "유용한 팁: {{proTip}} " }, @@ -7616,39 +7649,6 @@ }, "share-span": "공유" }, - "span-filters": { - "aria-label-select-max-span-operator": "최대 스팬 연산자 선택", - "aria-label-select-min-span-operator": "최소 스팬 연산자 선택", - "aria-label-select-service-name": "서비스 이름 선택", - "aria-label-select-service-name-operator": "서비스 이름 연산자 선택", - "aria-label-select-span-name": "스팬 이름 선택", - "aria-label-select-span-name-operator": "스팬 이름 연산자 선택", - "ariaLabel-select-max-span-duration": "최대 스팬 기간 선택", - "ariaLabel-select-min-span-duration": "최소 스팬 기간 선택", - "label-collapse": "스팬 필터", - "label-duration": "지속 시간", - "label-service-name": "서비스 이름", - "label-span-name": "스팬 이름", - "label-tags": "태그", - "placeholder-all-service-names": "모든 서비스 이름", - "placeholder-all-span-names": "모든 스팬 이름", - "tooltip-collapse": "아래에서 스팬을 필터링합니다. 결과 스팬을 가장 관심 있는 몇 가지로 좁힐 때까지 필터를 계속 적용할 수 있습니다.", - "tooltip-duration": "지속 시간을 기준으로 필터링합니다. 허용되는 단위는 {{units}}입니다", - "tooltip-tags": "스팬의 태그, 프로세스 태그 또는 로그 필드를 기준으로 필터링합니다." - }, - "span-filters-tags": { - "aria-label-add-tag": "태그 추가", - "aria-label-input-tag-value": "태그 값 입력", - "aria-label-remove-tag": "태그 제거", - "aria-label-select-tag-key": "태그 키 선택", - "aria-label-select-tag-operator": "태그 연산자 선택", - "aria-label-select-tag-value": "태그 값 선택", - "placeholder-select-tag": "태그 선택", - "placeholder-select-value": "값 선택", - "placeholder-tag-value": "태그 값", - "tooltip-add-tag": "태그 추가", - "tooltip-remove-tag": "태그 제거" - }, "span-flame-graph": { "flame-graph": "불꽃 그래프" }, @@ -7847,23 +7847,18 @@ "label-upsample": "업샘플", "tooltip-s-m-h": "10초, 1분, 30분, 1시간" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "쿼리 실행", - "modal-title": "SQL 편집기", "tooltip-experimental": "SQL 표현식 LLM 통합 기능은 실험 단계입니다. 문제가 발생하면 Grafana 팀에 보고해 주세요." }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "입력" @@ -7938,11 +7933,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9358,6 +9349,7 @@ "name-unit": "단위", "name-value-name": "값 이름", "name-y-axis-scale": "Y축 스케일", + "name-y-bucket-scale": "", "placeholder-axis-label": "자동", "placeholder-axis-width": "자동", "placeholder-decimals": "자동", @@ -9387,6 +9379,18 @@ "label-all": "전체", "label-hidden": "숨김", "label-single": "단일" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11157,9 +11161,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -11921,7 +11930,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "세부 정보 보기", "loading-finished-job": "완료된 작업 로딩 중...", @@ -12027,6 +12039,9 @@ "webhook-last-event": "마지막 이벤트:", "webhook-url": "웹훅 보기" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "리포지토리로 돌아가기", "cleaning-up-resources": "리포지토리 리소스 정리 및 삭제 중", @@ -12131,6 +12146,9 @@ "tooltip-unhealthy-repository": "상태가 좋지 않은 리포지토리를 가져올 수 없습니다" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12150,10 +12168,15 @@ }, "warning-title-default": "경고", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "이 프로세스의 지속 시간은 관련된 리소스 수에 따라 달라집니다.", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12172,6 +12195,7 @@ "step-finish": "추가 설정 선택", "step-synchronize": "외부 스토리지와 동기화", "sync-description": "외부 스토리지와 리소스를 동기화합니다. 이 일회성 단계 후에는 향후 모든 업데이트가 자동으로 리포지토리에 저장되고 인스턴스에 다시 프로비저닝됩니다.", + "sync-option-migrate-resources": "", "title-bootstrap": "동기화할 항목 선택", "title-connect": "외부 스토리지에 연결", "title-finish": "추가 설정 선택", @@ -12408,16 +12432,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index 237fbb74953..d5386284647 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -2666,6 +2666,35 @@ "text-federated": "Federatief", "text-provisioned": "Provisioned" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "Gegevensbron", @@ -3717,6 +3746,7 @@ "text": "Geen resultaten gevonden voor je zoekopdracht" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6180,7 +6210,7 @@ "title-someone-else-has-updated-this-dashboard": "Iemand anders heeft dit dashboard bijgewerkt", "would-still-dashboard": "Wil je dit dashboard nog steeds opslaan?" }, - "save-and-overwrite": "'Opslaan en overschrijven'" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7165,6 +7195,9 @@ "time-range-label": "Tijdsbereik vergrendelen" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "ProTip: {{proTip}} " }, @@ -7641,39 +7674,6 @@ }, "share-span": "Delen" }, - "span-filters": { - "aria-label-select-max-span-operator": "Operator voor maximale span selecteren", - "aria-label-select-min-span-operator": "Selecteer een operator voor een minimumspan", - "aria-label-select-service-name": "Servicenaam selecteren", - "aria-label-select-service-name-operator": "Servicenaam operator selecteren", - "aria-label-select-span-name": "Selecteer een spannaam", - "aria-label-select-span-name-operator": " Operator voor een spannaam selecteren", - "ariaLabel-select-max-span-duration": "Selecteer een maximale spanduur", - "ariaLabel-select-min-span-duration": "Selecteer een minimale spanduur", - "label-collapse": "Spanfilters", - "label-duration": "Duur", - "label-service-name": "Servicenaam", - "label-span-name": "Spannaam", - "label-tags": "Labels", - "placeholder-all-service-names": "Alle servicenamen", - "placeholder-all-span-names": "Alle spannamen", - "tooltip-collapse": "Filter je bereik hieronder. Je kunt filters blijven toepassen totdat je het resulterende bereik hebt beperkt tot de resultaten die het meest interessant voor je zijn.", - "tooltip-duration": "Filteren op duur. Geaccepteerde eenheden zijn {{units}}", - "tooltip-tags": "Filteren op labels, labels verwerken of logvelden in je spans." - }, - "span-filters-tags": { - "aria-label-add-tag": "Label toevoegen", - "aria-label-input-tag-value": "Labelwaarde invoeren", - "aria-label-remove-tag": "Label verwijderen", - "aria-label-select-tag-key": "Labelsleutel selecteren", - "aria-label-select-tag-operator": "Labeloperator selecteren", - "aria-label-select-tag-value": "Labelwaarde selecteren", - "placeholder-select-tag": "Label selecteren", - "placeholder-select-value": "Waarde selecteren", - "placeholder-tag-value": "Labelwaarde", - "tooltip-add-tag": "Label toevoegen", - "tooltip-remove-tag": "Label verwijderen" - }, "span-flame-graph": { "flame-graph": "Vlamgrafiek" }, @@ -7872,23 +7872,18 @@ "label-upsample": "Upsample", "tooltip-s-m-h": "10s, 1m, 30m, 1u" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "Query uitvoeren", - "modal-title": "SQL-editor", "tooltip-experimental": "SQL Expressions LLM-integratie is experimenteel. Meld eventuele problemen aan het Grafana-team." }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "Invoer" @@ -7963,11 +7958,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9393,6 +9384,7 @@ "name-unit": "Eenheid", "name-value-name": "Naam van waarde:", "name-y-axis-scale": "Y-as schaal", + "name-y-bucket-scale": "", "placeholder-axis-label": "Automatisch", "placeholder-axis-width": "Automatisch", "placeholder-decimals": "Automatisch", @@ -9422,6 +9414,18 @@ "label-all": "Alle", "label-hidden": "Verborgen", "label-single": "Enkel" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11203,9 +11207,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -11972,7 +11981,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "Details bekijken", "loading-finished-job": "Voltooide taak laden...", @@ -12078,6 +12090,9 @@ "webhook-last-event": "Laatste gebeurtenis:", "webhook-url": "Webhook bekijken" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "Terug naar repositories", "cleaning-up-resources": "Bronnen van repository opschonen", @@ -12182,6 +12197,9 @@ "tooltip-unhealthy-repository": "Kan geen ongezonde repository ophalen" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12201,10 +12219,15 @@ }, "warning-title-default": "Waarschuwing", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "De duur van dit proces is afhankelijk van het aantal betrokken bronnen.", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12223,6 +12246,7 @@ "step-finish": "Kies aanvullende instellingen", "step-synchronize": "Synchroniseren met externe opslag", "sync-description": "Bronnen synchroniseren met externe opslag. Na deze eenmalige stap worden alle toekomstige updates automatisch opgeslagen in de repository en opnieuw ingericht in de instantie.", + "sync-option-migrate-resources": "", "title-bootstrap": "Kies wat je wilt synchroniseren", "title-connect": "Verbinden met externe opslag", "title-finish": "Kies aanvullende instellingen", @@ -12460,16 +12484,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 6ff40c3ab1f..ad8f9b19b6a 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -2688,6 +2688,35 @@ "text-federated": "Federacja", "text-provisioned": "Po aprowizacji" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "Źródło danych", @@ -3749,6 +3778,7 @@ "text": "Nie znaleziono wyników dla tego zapytania" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6222,7 +6252,7 @@ "title-someone-else-has-updated-this-dashboard": "Ktoś inny zaktualizował ten pulpit", "would-still-dashboard": "Czy nadal chcesz zapisać ten pulpit?" }, - "save-and-overwrite": "„Zapisz i zastąp”" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7211,6 +7241,9 @@ "time-range-label": "Zablokuj zakres czasu" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "Wskazówka: {{proTip}}" }, @@ -7691,39 +7724,6 @@ }, "share-span": "Udostępnij" }, - "span-filters": { - "aria-label-select-max-span-operator": "Wybierz operator maksymalnego zakresu", - "aria-label-select-min-span-operator": "Wybierz operator minimalnego zakresu", - "aria-label-select-service-name": "Wybierz nazwę usługi", - "aria-label-select-service-name-operator": "Wybierz operator nazwy usługi", - "aria-label-select-span-name": "Wybierz nazwę zakresu", - "aria-label-select-span-name-operator": "Wybierz operator nazwy zakresu", - "ariaLabel-select-max-span-duration": "Wybierz maksymalny czas trwania zakresu", - "ariaLabel-select-min-span-duration": "Wybierz minimalny czas trwania zakresu", - "label-collapse": "Filtry zakresu", - "label-duration": "Czas trwania", - "label-service-name": "Nazwa usługi", - "label-span-name": "Nazwa zakresu", - "label-tags": "Znaczniki", - "placeholder-all-service-names": "Wszystkie nazwy usług", - "placeholder-all-span-names": "Wszystkie nazwy zakresów", - "tooltip-collapse": "Odfiltruj zakresy poniżej. Możesz dodawać filtry, aż zawęzisz zakres wyników do kilku najbardziej interesujących.", - "tooltip-duration": "Filtrowanie według czasu trwania. Akceptowane jednostki: {{units}}", - "tooltip-tags": "Filtruj według tagów, tagów procesu lub pól logów w swoich zakresach." - }, - "span-filters-tags": { - "aria-label-add-tag": "Dodaj tag", - "aria-label-input-tag-value": "Wpisz wartość tagu", - "aria-label-remove-tag": "Usuń tag", - "aria-label-select-tag-key": "Wybierz klucz tagu", - "aria-label-select-tag-operator": "Wybierz operator tagu", - "aria-label-select-tag-value": "Wybierz wartość tagu", - "placeholder-select-tag": "Wybierz tag", - "placeholder-select-value": "Wybierz wartość", - "placeholder-tag-value": "Wartość tagu", - "tooltip-add-tag": "Dodaj tag", - "tooltip-remove-tag": "Usuń tag" - }, "span-flame-graph": { "flame-graph": "Wykres płomienia" }, @@ -7922,23 +7922,18 @@ "label-upsample": "Zwiększ próbkowanie", "tooltip-s-m-h": "10s, 1m, 30m, 1h" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "Uruchom zapytanie", - "modal-title": "Edytor SQL", "tooltip-experimental": "Integracja LLM z wyrażeniami SQL jest eksperymentalna. Wszelkie problemy zgłaszaj zespołowi Grafana." }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "Wejście" @@ -8013,11 +8008,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9463,6 +9454,7 @@ "name-unit": "Jednostka", "name-value-name": "Nazwa wartości", "name-y-axis-scale": "Skala osi Y", + "name-y-bucket-scale": "", "placeholder-axis-label": "Automatycznie", "placeholder-axis-width": "Automatycznie", "placeholder-decimals": "Automatycznie", @@ -9492,6 +9484,18 @@ "label-all": "Wszystkie", "label-hidden": "Ukryte", "label-single": "Pojedyncza" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11295,9 +11299,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -12074,7 +12083,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "Zobacz szczegóły", "loading-finished-job": "Wczytywanie zakończonego zadania…", @@ -12180,6 +12192,9 @@ "webhook-last-event": "Ostatnie zdarzenie:", "webhook-url": "Wyświetl element webhook" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "Wróć do repozytoriów", "cleaning-up-resources": "Sprzątanie zasobów repozytorium", @@ -12284,6 +12299,9 @@ "tooltip-unhealthy-repository": "Nie można pobrać danych z niesprawnego repozytorium" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12303,10 +12321,15 @@ }, "warning-title-default": "Ostrzeżenie", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "Czas trwania tego procesu zależy od liczby zasobów.", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12325,6 +12348,7 @@ "step-finish": "Wybierz dodatkowe ustawienia", "step-synchronize": "Synchronizuj z zewnętrzną pamięcią masową", "sync-description": "Zsynchronizuj zasoby z zewnętrzną pamięcią masową. Po tym jednorazowym kroku wszystkie przyszłe aktualizacje zostaną automatycznie zapisane w repozytorium i ponownie aprowizowane w instancji.", + "sync-option-migrate-resources": "", "title-bootstrap": "Wybierz, co chcesz zsynchronizować", "title-connect": "Połączenie z zewnętrzną pamięcią masową", "title-finish": "Wybierz dodatkowe ustawienia", @@ -12564,16 +12588,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index cbaee920be0..8ad480fc30d 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -2666,6 +2666,35 @@ "text-federated": "Federado", "text-provisioned": "Provisionado" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "Fonte de dados", @@ -3717,6 +3746,7 @@ "text": "Nenhum resultado encontrado para sua consulta" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6180,7 +6210,7 @@ "title-someone-else-has-updated-this-dashboard": "Outra pessoa atualizou este painel", "would-still-dashboard": "Deseja salvar este painel mesmo assim?" }, - "save-and-overwrite": "\"Salvar e substituir\"" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7165,6 +7195,9 @@ "time-range-label": "Bloquear intervalo de tempo" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "Dica de especialistas: {{proTip}}" }, @@ -7641,39 +7674,6 @@ }, "share-span": "Compartilhar" }, - "span-filters": { - "aria-label-select-max-span-operator": "Selecionar operador de intervalo máximo", - "aria-label-select-min-span-operator": "Selecionar operador de intervalo mínimo", - "aria-label-select-service-name": "Selecionar nome do serviço", - "aria-label-select-service-name-operator": "Selecionar operador de nome do serviço", - "aria-label-select-span-name": "Selecionar nome do intervalo", - "aria-label-select-span-name-operator": "Selecione o operador de nome de intervalo", - "ariaLabel-select-max-span-duration": "Selecionar duração máxima do intervalo", - "ariaLabel-select-min-span-duration": "Selecionar duração mínima do intervalo", - "label-collapse": "Filtros de intervalo", - "label-duration": "Duração", - "label-service-name": "Nome do serviço", - "label-span-name": "Nome do intervalo", - "label-tags": "Tags", - "placeholder-all-service-names": "Todos os nomes de serviço", - "placeholder-all-span-names": "Todos os nomes de intervalo", - "tooltip-collapse": "Filtre seus intervalos abaixo. Você pode continuar aplicando filtros até restringir seus intervalos resultantes a um grupo pequeno que contenha aqueles que forem mais pertinentes para você.", - "tooltip-duration": "Filtrar por duração. As unidades aceitas são {{units}}", - "tooltip-tags": "Filtrar por tags, tags de processo ou campos de logs nos seus intervalos." - }, - "span-filters-tags": { - "aria-label-add-tag": "Adicionar tag", - "aria-label-input-tag-value": "Valor da tag de entrada", - "aria-label-remove-tag": "Remover tag", - "aria-label-select-tag-key": "Selecionar chave de tag", - "aria-label-select-tag-operator": "Selecionar operador de tag", - "aria-label-select-tag-value": "Selecionar valor da tag", - "placeholder-select-tag": "Selecionar tag", - "placeholder-select-value": "Selecionar valor", - "placeholder-tag-value": "Valor da tag", - "tooltip-add-tag": "Adicionar tag", - "tooltip-remove-tag": "Remover tag" - }, "span-flame-graph": { "flame-graph": "Gráfico de chama" }, @@ -7872,23 +7872,18 @@ "label-upsample": "Aumentar taxa de amostragem", "tooltip-s-m-h": "10s, 1m, 30m, 1h" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "Executar consulta", - "modal-title": "Editor de SQL", "tooltip-experimental": "A integração do LLM de expressões SQL está em fase de testes. Informe à equipe do Grafana se surgir algum problema." }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "Entrada" @@ -7963,11 +7958,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9393,6 +9384,7 @@ "name-unit": "Unidade", "name-value-name": "Nome do valor", "name-y-axis-scale": "Escala do eixo Y", + "name-y-bucket-scale": "", "placeholder-axis-label": "Automático", "placeholder-axis-width": "Automático", "placeholder-decimals": "Automático", @@ -9422,6 +9414,18 @@ "label-all": "Tudo", "label-hidden": "Oculto", "label-single": "Única" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11203,9 +11207,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -11972,7 +11981,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "Veja os detalhes", "loading-finished-job": "Carregando tarefa concluída…", @@ -12078,6 +12090,9 @@ "webhook-last-event": "Último evento:", "webhook-url": "Visualizar Webhook" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "Voltar para os repositórios", "cleaning-up-resources": "Limpando recursos do repositório", @@ -12182,6 +12197,9 @@ "tooltip-unhealthy-repository": "Não é possível fazer extração de um repositório instável" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12201,10 +12219,15 @@ }, "warning-title-default": "Aviso", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "A duração deste processo depende da quantidade de recursos envolvidos.", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12223,6 +12246,7 @@ "step-finish": "Escolha configurações adicionais", "step-synchronize": "Sincronizar com armazenamento externo", "sync-description": "Sincronize recursos com armazenamento externo. Após esta única etapa, todas as atualizações futuras serão salvas automaticamente no repositório e provisionadas de volta para a instância.", + "sync-option-migrate-resources": "", "title-bootstrap": "Escolha o que será sincronizado", "title-connect": "Conectar ao armazenamento externo", "title-finish": "Escolha configurações adicionais", @@ -12460,16 +12484,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index 4e99ebc24f7..3fed004bfdf 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -2666,6 +2666,35 @@ "text-federated": "Federado", "text-provisioned": "Aprovisionado" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "Origem dos dados", @@ -3717,6 +3746,7 @@ "text": "Não foram encontrados resultados para a sua consulta" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6180,7 +6210,7 @@ "title-someone-else-has-updated-this-dashboard": "Outra pessoa atualizou este painel de controlo", "would-still-dashboard": "Ainda pretende guardar este painel de controlo?" }, - "save-and-overwrite": "\"Guardar e substituir\"" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7165,6 +7195,9 @@ "time-range-label": "Bloquear intervalo de tempo" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "Dica Pro: {{proTip}}" }, @@ -7641,39 +7674,6 @@ }, "share-span": "Partilhar" }, - "span-filters": { - "aria-label-select-max-span-operator": "Selecionar o operador de intervalo máximo", - "aria-label-select-min-span-operator": "Selecionar o operador de intervalo mínimo", - "aria-label-select-service-name": "Selecione o nome do serviço", - "aria-label-select-service-name-operator": "Selecionar o operador do nome do serviço", - "aria-label-select-span-name": "Selecionar o nome do intervalo", - "aria-label-select-span-name-operator": "Selecionar o operador do nome do intervalo", - "ariaLabel-select-max-span-duration": "Selecionar a duração máxima do intervalo", - "ariaLabel-select-min-span-duration": "Selecionar a duração mínima do intervalo", - "label-collapse": "Filtros de intervalo", - "label-duration": "Duração", - "label-service-name": "Nome do serviço", - "label-span-name": "Nome do intervalo", - "label-tags": "Etiquetas", - "placeholder-all-service-names": "Todos os nomes de serviços", - "placeholder-all-span-names": "Todos os nomes de intervalos", - "tooltip-collapse": "Filtre os seus intervalos abaixo. Pode continuar a aplicar filtros até ter limitado os seus períodos resultantes para apenas alguns que lhe interessem mais.", - "tooltip-duration": "Filtrar por duração. As unidades aceites são {{units}}", - "tooltip-tags": "Filtrar por etiquetas, etiquetas de processo ou campos de registo nos seus intervalos." - }, - "span-filters-tags": { - "aria-label-add-tag": "Adicionar etiqueta", - "aria-label-input-tag-value": "Valor da etiqueta de entrada", - "aria-label-remove-tag": "Remover controlo", - "aria-label-select-tag-key": "Selecionar a chave da etiqueta", - "aria-label-select-tag-operator": "Selecionar o operador da etiqueta", - "aria-label-select-tag-value": "Selecionar o valor da etiqueta", - "placeholder-select-tag": "Selecionar a etiqueta", - "placeholder-select-value": "Selecionar valor", - "placeholder-tag-value": "Valor da etiqueta", - "tooltip-add-tag": "Adicionar etiqueta", - "tooltip-remove-tag": "Remover controlo" - }, "span-flame-graph": { "flame-graph": "Gráfico de chama" }, @@ -7872,23 +7872,18 @@ "label-upsample": "Aumentar a quantidade de amostras", "tooltip-s-m-h": "10s, 1m, 30m, 1h" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "Executar consulta", - "modal-title": "Editor SQL", "tooltip-experimental": "A integração de LLM de expressões SQL é experimental. Comunique quaisquer problemas à equipa da Grafana." }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "Entrada" @@ -7963,11 +7958,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9393,6 +9384,7 @@ "name-unit": "Unidade", "name-value-name": "Nome do valor", "name-y-axis-scale": "Escala do eixo Y", + "name-y-bucket-scale": "", "placeholder-axis-label": "Automático", "placeholder-axis-width": "Automático", "placeholder-decimals": "Automático", @@ -9422,6 +9414,18 @@ "label-all": "Tudo", "label-hidden": "Oculto", "label-single": "Único" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11203,9 +11207,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -11972,7 +11981,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "Ver detalhes", "loading-finished-job": "A carregar o trabalho concluído...", @@ -12078,6 +12090,9 @@ "webhook-last-event": "Último evento:", "webhook-url": "Visualizar Webhook" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "Voltar aos repositórios", "cleaning-up-resources": "A limpar recursos do repositório", @@ -12182,6 +12197,9 @@ "tooltip-unhealthy-repository": "Não foi possível obter um repositório que não está em bom estado" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12201,10 +12219,15 @@ }, "warning-title-default": "Aviso", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "A duração deste processo depende do número de recursos envolvidos.", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12223,6 +12246,7 @@ "step-finish": "Escolha as definições adicionais", "step-synchronize": "Sincronizar com armazenamento externo", "sync-description": "Sincronize recursos com um armazenamento externo. Após este passo único, todas as atualizações futuras serão guardadas automaticamente no repositório e aprovisionadas novamente para a instância.", + "sync-option-migrate-resources": "", "title-bootstrap": "Escolha o que sincronizar", "title-connect": "Ligar a um armazenamento externo", "title-finish": "Escolha as definições adicionais", @@ -12460,16 +12484,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index 3d29410a1bf..10cd0cdd7bb 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -2688,6 +2688,35 @@ "text-federated": "Федеративная", "text-provisioned": "Подготовлено" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "Источник данных", @@ -3749,6 +3778,7 @@ "text": "По вашему запросу ничего не найдено" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6222,7 +6252,7 @@ "title-someone-else-has-updated-this-dashboard": "Дашборд обновлен другим пользователем", "would-still-dashboard": "Все равно сохранить дашборд?" }, - "save-and-overwrite": "'Сохранить и перезаписать'" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7211,6 +7241,9 @@ "time-range-label": "Заблокировать временной диапазон" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "Совет: {{proTip}}" }, @@ -7691,39 +7724,6 @@ }, "share-span": "Поделиться" }, - "span-filters": { - "aria-label-select-max-span-operator": "Выбрать оператора макс. диапазона", - "aria-label-select-min-span-operator": "Выбрать оператор мин. диапазона", - "aria-label-select-service-name": "Выбрать название службы", - "aria-label-select-service-name-operator": "Выбрать оператор названия службы", - "aria-label-select-span-name": "Выбрать название диапазона", - "aria-label-select-span-name-operator": "Выбрать оператора названия диапазона", - "ariaLabel-select-max-span-duration": "Выбрать макс. продолжительность интервала", - "ariaLabel-select-min-span-duration": "Выбрать мин. продолжительность интервала", - "label-collapse": "Фильтры диапазонов", - "label-duration": "Длительность", - "label-service-name": "Название службы", - "label-span-name": "Название диапазона", - "label-tags": "Теги", - "placeholder-all-service-names": "Все названия служб", - "placeholder-all-span-names": "Все названия диапазонов", - "tooltip-collapse": "Выполните фильтрацию своих диапазонов ниже. Вы можете продолжать применять фильтры, пока не сузите полученные диапазоны до нескольких наиболее важных для вас вариантов.", - "tooltip-duration": "Фильтр по длительности. Допустимые единицы измерения: {{units}}", - "tooltip-tags": "Фильтр по тегам, тегам процессов или полям журнала в ваших диапазонах." - }, - "span-filters-tags": { - "aria-label-add-tag": "Добавить тег", - "aria-label-input-tag-value": "Ввести значение тега", - "aria-label-remove-tag": "Удалить тег", - "aria-label-select-tag-key": "Выбрать ключ тега", - "aria-label-select-tag-operator": "Выбрать оператор тега", - "aria-label-select-tag-value": "Выбрать значение тега", - "placeholder-select-tag": "Выбрать тег", - "placeholder-select-value": "Выбрать значение", - "placeholder-tag-value": "Значение тега", - "tooltip-add-tag": "Добавить тег", - "tooltip-remove-tag": "Удалить тег" - }, "span-flame-graph": { "flame-graph": "Flame-график" }, @@ -7922,23 +7922,18 @@ "label-upsample": "Увеличить частоту выборки", "tooltip-s-m-h": "10 с, 1 мин, 30 мин, 1 ч" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "Выполнить запрос", - "modal-title": "Редактор SQL", "tooltip-experimental": "Интеграция LLM с SQL-выражениями является экспериментальной. При обнаружении проблем свяжитесь с командой Grafana." }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "Ввод" @@ -8013,11 +8008,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9463,6 +9454,7 @@ "name-unit": "Единица", "name-value-name": "Имя значения", "name-y-axis-scale": "Шкала оси Y", + "name-y-bucket-scale": "", "placeholder-axis-label": "Авто", "placeholder-axis-width": "Авто", "placeholder-decimals": "Авто", @@ -9492,6 +9484,18 @@ "label-all": "Все", "label-hidden": "Скрыты", "label-single": "Один" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11295,9 +11299,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -12074,7 +12083,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "Подробнее", "loading-finished-job": "Загрузка завершенного задания...", @@ -12180,6 +12192,9 @@ "webhook-last-event": "Последнее событие:", "webhook-url": "Просмотр веб-перехватчика" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "Назад к репозиториям", "cleaning-up-resources": "Очистка ресурсов репозитория", @@ -12284,6 +12299,9 @@ "tooltip-unhealthy-repository": "Невозможно внести изменения в неисправный репозиторий" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12303,10 +12321,15 @@ }, "warning-title-default": "Предупреждение", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "Длительность процесса зависит от количества задействованных ресурсов.", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12325,6 +12348,7 @@ "step-finish": "Выберите дополнительные параметры", "step-synchronize": "Синхронизировать с внешним хранилищем", "sync-description": "Синхронизируйте ресурсы с внешним хранилищем. После этого разового шага все будущие обновления будут автоматически сохраняться в репозитории и загружаться обратно в экземпляр.", + "sync-option-migrate-resources": "", "title-bootstrap": "Выберите, что синхронизировать", "title-connect": "Подключение к внешнему хранилищу", "title-finish": "Выбор дополнительных параметров", @@ -12564,16 +12588,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 38943db06ed..faca6a40afc 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -2666,6 +2666,35 @@ "text-federated": "Federerade", "text-provisioned": "Provisionerad" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "Datakälla", @@ -3717,6 +3746,7 @@ "text": "Inga resultat hittades för din fråga" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6180,7 +6210,7 @@ "title-someone-else-has-updated-this-dashboard": "Någon annan har uppdaterat denna instrumentpanel", "would-still-dashboard": "Vill du fortfarande spara denna instrumentpanel?" }, - "save-and-overwrite": "”Spara och skriv över”" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7165,6 +7195,9 @@ "time-range-label": "Lås tidsintervall" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "ProTip: {{proTip}}" }, @@ -7641,39 +7674,6 @@ }, "share-span": "Dela" }, - "span-filters": { - "aria-label-select-max-span-operator": "Välj operator för maximalt intervall", - "aria-label-select-min-span-operator": "Välj operator för minsta intervall", - "aria-label-select-service-name": "Välj namn för tjänst", - "aria-label-select-service-name-operator": "Välj operator för tjänstnamn", - "aria-label-select-span-name": "Välj namn på intervall", - "aria-label-select-span-name-operator": "Välj operator för intervallnamn", - "ariaLabel-select-max-span-duration": "Välj maximal varaktighet för tidsspann", - "ariaLabel-select-min-span-duration": "Välj minimal varaktighet för tidsspann", - "label-collapse": "Intervallfilter", - "label-duration": "Varaktighet", - "label-service-name": "Namn på tjänst", - "label-span-name": "Intervallnamn", - "label-tags": "Taggar", - "placeholder-all-service-names": "Alla servicenamn", - "placeholder-all-span-names": "Alla namn på intervall", - "tooltip-collapse": "Filtrera dina spann nedan. Du kan fortsätta att tillämpa filter tills du har begränsat dina resulterande spann till det fåtal som du är mest intresserad av.", - "tooltip-duration": "Filtrera per varaktighet. Accepterade enheter är {{units}}", - "tooltip-tags": "Filtrera efter taggar, processtaggar eller loggfält i dina spår." - }, - "span-filters-tags": { - "aria-label-add-tag": "Lägg till etikett", - "aria-label-input-tag-value": "Ange taggvärde", - "aria-label-remove-tag": "Ta bort tagg", - "aria-label-select-tag-key": "Välj etikettnyckel", - "aria-label-select-tag-operator": "Välj etikettoperator", - "aria-label-select-tag-value": "Välj etikettvärde", - "placeholder-select-tag": "Välj etikett", - "placeholder-select-value": "Välj värde", - "placeholder-tag-value": "Etikettvärde", - "tooltip-add-tag": "Lägg till etikett", - "tooltip-remove-tag": "Ta bort tagg" - }, "span-flame-graph": { "flame-graph": "Flamgraf" }, @@ -7872,23 +7872,18 @@ "label-upsample": "Sampla upp", "tooltip-s-m-h": "10 s, 1 m, 30 m, 1 h" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "Kör fråga", - "modal-title": "SQL-redigerare", "tooltip-experimental": "Integreringen för SQL Expressions LLM är i ett experimentellt skede. Rapportera alla problem du stöter på till Grafana-teamet." }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "Ingång" @@ -7963,11 +7958,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9393,6 +9384,7 @@ "name-unit": "Enhet", "name-value-name": "Värdenamn", "name-y-axis-scale": "Y-axelns skala", + "name-y-bucket-scale": "", "placeholder-axis-label": "Auto", "placeholder-axis-width": "Auto", "placeholder-decimals": "Auto", @@ -9422,6 +9414,18 @@ "label-all": "Alla", "label-hidden": "Dolt", "label-single": "Singel" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11203,9 +11207,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -11972,7 +11981,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "Visa detaljer", "loading-finished-job": "Läser in färdigt jobb …", @@ -12078,6 +12090,9 @@ "webhook-last-event": "Senaste händelsen:", "webhook-url": "Visa webhook" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "Tillbaka till lagringsplatserna", "cleaning-up-resources": "Rensa lagringsplatsresurser", @@ -12182,6 +12197,9 @@ "tooltip-unhealthy-repository": "Det gick inte att hämta en ohälsosam lagringsplats" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12201,10 +12219,15 @@ }, "warning-title-default": "Varning", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "Hur lång den här processen är beror på hur många resurser som är inblandade.", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12223,6 +12246,7 @@ "step-finish": "Välj ytterligare inställningar", "step-synchronize": "Synkronisera med extern lagring", "sync-description": "Synkronisera resurser med extern lagring. Efter det här engångssteget sparas alla framtida uppdateringar automatiskt på lagringsplatsen och provisioneras tillbaka till instansen.", + "sync-option-migrate-resources": "", "title-bootstrap": "Välj vad du vill synkronisera", "title-connect": "Anslut till extern lagring", "title-finish": "Välj ytterligare inställningar", @@ -12460,16 +12484,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 07a0b6673ac..ad957bd271f 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -2666,6 +2666,35 @@ "text-federated": "Şirket dışı", "text-provisioned": "Sağlanan" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "Veri kaynağı", @@ -3717,6 +3746,7 @@ "text": "Sorgunuz için sonuç bulunamadı" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6180,7 +6210,7 @@ "title-someone-else-has-updated-this-dashboard": "Başka biri bu panoyu güncelledi", "would-still-dashboard": "Yine de bu panoyu kaydetmek istiyor musunuz?" }, - "save-and-overwrite": "\"Kaydet ve üzerine yaz\"" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7165,6 +7195,9 @@ "time-range-label": "Zaman aralığını kilitle" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "Uzman ipucu: {{proTip}}" }, @@ -7641,39 +7674,6 @@ }, "share-span": "Paylaş" }, - "span-filters": { - "aria-label-select-max-span-operator": "Maksimum zaman aralığı işlecini seçin", - "aria-label-select-min-span-operator": "Minimum zaman aralığı işlecini seçin", - "aria-label-select-service-name": "Hizmet adını seç", - "aria-label-select-service-name-operator": "Hizmet adını işlecini seç", - "aria-label-select-span-name": "Zaman aralığı adı seç", - "aria-label-select-span-name-operator": "Zaman aralığı adı işleci seçin", - "ariaLabel-select-max-span-duration": "Maksimum zaman aralığı süresini seçin", - "ariaLabel-select-min-span-duration": "Minimum zaman aralığı süresini seçin", - "label-collapse": "Zaman aralığı filtreleri", - "label-duration": "Süre", - "label-service-name": "Hizmet adı", - "label-span-name": "Zaman aralığı adı", - "label-tags": "Etiketler", - "placeholder-all-service-names": "Tüm hizmet adları", - "placeholder-all-span-names": "Tüm zaman aralığı adları", - "tooltip-collapse": "Aşağıdaki zaman aralıklarınızı filtreleyin. İlginizi en çok çeken birkaç sonucu daraltana kadar filtre uygulamaya devam edebilirsiniz.", - "tooltip-duration": "Süreye göre filtreleyin. Kabul edilen birimler: {{units}}", - "tooltip-tags": "Zamanlardaki etiketlere, işlem etiketlerine veya günlük kaydı alanlarına göre filtreleyin." - }, - "span-filters-tags": { - "aria-label-add-tag": "Etiket ekle", - "aria-label-input-tag-value": "Etiket değeri girin", - "aria-label-remove-tag": "Etiketi kaldır", - "aria-label-select-tag-key": "Etiket anahtarını seçin", - "aria-label-select-tag-operator": "Etiket işlecini seçin", - "aria-label-select-tag-value": "Etiket değerini seçin", - "placeholder-select-tag": "Etiket seçin", - "placeholder-select-value": "Değer seçin", - "placeholder-tag-value": "Etiket değeri", - "tooltip-add-tag": "Etiket ekle", - "tooltip-remove-tag": "Etiketi kaldır" - }, "span-flame-graph": { "flame-graph": "Alev grafiği" }, @@ -7872,23 +7872,18 @@ "label-upsample": "Örneklemeyi artır", "tooltip-s-m-h": "10 sn, 1 dk, 30 dk, 1 sa" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "", - "modal-title": "", "tooltip-experimental": "" }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "Girdi" @@ -7963,11 +7958,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9393,6 +9384,7 @@ "name-unit": "Birim", "name-value-name": "Değer adı", "name-y-axis-scale": "Y ekseni ölçeği", + "name-y-bucket-scale": "", "placeholder-axis-label": "Otomatik", "placeholder-axis-width": "Otomatik", "placeholder-decimals": "Otomatik", @@ -9422,6 +9414,18 @@ "label-all": "Tümü", "label-hidden": "Gizli", "label-single": "Tek" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11203,9 +11207,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -11972,7 +11981,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "Ayrıntıları görüntüleyin", "loading-finished-job": "Tamamlanan iş yükleniyor...", @@ -12078,6 +12090,9 @@ "webhook-last-event": "Son Olay:", "webhook-url": "Web Kancasını Görüntüle" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "Depolara geri dön", "cleaning-up-resources": "Depo kaynakları temizleniyor", @@ -12182,6 +12197,9 @@ "tooltip-unhealthy-repository": "İyi durumda olmayan bir depo çekilemedi" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12201,10 +12219,15 @@ }, "warning-title-default": "Uyarı", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "Bu işlemin süresi dâhil olan kaynakların sayısına bağlıdır.", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12223,6 +12246,7 @@ "step-finish": "Ek ayarlar seçin", "step-synchronize": "Harici depolama ile senkronize et", "sync-description": "Kaynakları harici depolama ile senkronize edin. Bu tek seferlik adımdan sonra tüm gelecekteki güncellemeler otomatik olarak depoya kaydedilecek ve örneğe geri sağlanacaktır.", + "sync-option-migrate-resources": "", "title-bootstrap": "Nelerin senkronize edileceğini seçin", "title-connect": "Harici depolamaya bağlan", "title-finish": "Ek ayarlar seçin", @@ -12460,16 +12484,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index a1eb5ea1fae..f02bcda5189 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -2655,6 +2655,35 @@ "text-federated": "联合", "text-provisioned": "已预置" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "数据源", @@ -3701,6 +3730,7 @@ "text": "未找到与您的查询相关的结果" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6159,7 +6189,7 @@ "title-someone-else-has-updated-this-dashboard": "其他人已更新此数据面板", "would-still-dashboard": "您仍然要保存此数据面板吗?" }, - "save-and-overwrite": "‘保存并覆盖’" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7142,6 +7172,9 @@ "time-range-label": "锁定时间范围" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "专业提示:{{proTip}}" }, @@ -7616,39 +7649,6 @@ }, "share-span": "共享" }, - "span-filters": { - "aria-label-select-max-span-operator": "选择最大跨度运算符", - "aria-label-select-min-span-operator": "选择最小跨度运算符", - "aria-label-select-service-name": "选择服务名称", - "aria-label-select-service-name-operator": "选择服务名称运算符", - "aria-label-select-span-name": "选择跨度名称", - "aria-label-select-span-name-operator": "选择跨度名称运算符", - "ariaLabel-select-max-span-duration": "选择最大跨度持续时间", - "ariaLabel-select-min-span-duration": "选择最小跨度持续时间", - "label-collapse": "跨度筛选器", - "label-duration": "持续时间", - "label-service-name": "服务名称", - "label-span-name": "跨度名称", - "label-tags": "标签", - "placeholder-all-service-names": "所有服务名称", - "placeholder-all-span-names": "所有跨度名称", - "tooltip-collapse": "在下方筛选您的跨度。您可以继续应用筛选条件,直到将结果跨度缩小到您最感兴趣的少数几个。", - "tooltip-duration": "按持续时间筛选。可接受的单位是 {{units}}", - "tooltip-tags": "根据跨度中的标记、流程标记或日志字段进行筛选。" - }, - "span-filters-tags": { - "aria-label-add-tag": "添加标记", - "aria-label-input-tag-value": "输入标记值", - "aria-label-remove-tag": "移除标记", - "aria-label-select-tag-key": "选择标记键", - "aria-label-select-tag-operator": "选择标记运算符", - "aria-label-select-tag-value": "选择标记值", - "placeholder-select-tag": "选择标记", - "placeholder-select-value": "选择值", - "placeholder-tag-value": "标记值", - "tooltip-add-tag": "添加标记", - "tooltip-remove-tag": "移除标记" - }, "span-flame-graph": { "flame-graph": "火焰图" }, @@ -7847,23 +7847,18 @@ "label-upsample": "升高采样", "tooltip-s-m-h": "10 秒、1 分钟、30 分钟、1 小时" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "运行查询", - "modal-title": "SQL 编辑器", "tooltip-experimental": "SQL 表达式 LLM 集成是实验性的。若有任何问题,请向 Grafana 团队报告。" }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "输入" @@ -7938,11 +7933,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9358,6 +9349,7 @@ "name-unit": "单位", "name-value-name": "值名称", "name-y-axis-scale": "Y 轴比例", + "name-y-bucket-scale": "", "placeholder-axis-label": "自动", "placeholder-axis-width": "自动", "placeholder-decimals": "自动", @@ -9387,6 +9379,18 @@ "label-all": "全部", "label-hidden": "隐藏", "label-single": "单一" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11157,9 +11161,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -11921,7 +11930,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "查看详情", "loading-finished-job": "正在加载已完成的作业...", @@ -12027,6 +12039,9 @@ "webhook-last-event": "最后一个事件:", "webhook-url": "查看 Webhook" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "回到存储库", "cleaning-up-resources": "清理存储库资源", @@ -12131,6 +12146,9 @@ "tooltip-unhealthy-repository": "无法拉取状态不良的存储库" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12150,10 +12168,15 @@ }, "warning-title-default": "警告", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "此过程的持续时间取决于所涉及资源的数量。", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12172,6 +12195,7 @@ "step-finish": "选择附加设置", "step-synchronize": "与外部存储同步", "sync-description": "将资源与外部存储同步。完成此一次性步骤后,所有后续更新都将自动保存到存储库中,并预配回实例。", + "sync-option-migrate-resources": "", "title-bootstrap": "选择要同步的内容", "title-connect": "连接到外部存储", "title-finish": "选择附加设置", @@ -12408,16 +12432,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index df0a64f1c1e..dc05987a2e6 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -2655,6 +2655,35 @@ "text-federated": "聯合", "text-provisioned": "已佈建" }, + "saved-searches": { + "actions-aria-label": "", + "apply-aria-label": "", + "apply-tooltip": "", + "button-label": "", + "cancel": "", + "default-indicator": "", + "delete": "", + "delete-button": "", + "dropdown-aria-label": "", + "empty-state": "", + "error-load-description": "", + "error-load-title": "", + "error-name-duplicate": "", + "error-name-required": "", + "error-rename-description": "", + "error-rename-title": "", + "error-save-description": "", + "error-save-title": "", + "list-aria-label": "", + "name-placeholder": "", + "remove-default": "", + "rename": "", + "rename-button": "", + "save-button": "", + "save-current-search": "", + "save-disabled-tooltip": "", + "set-default": "" + }, "search": { "property": { "data-source": "資料來源", @@ -3701,6 +3730,7 @@ "text": "未找到您的查詢結果" }, "recently-viewed": { + "clear": "", "empty": "", "title": "" }, @@ -6159,7 +6189,7 @@ "title-someone-else-has-updated-this-dashboard": "其他人已更新此儀表板", "would-still-dashboard": "仍要儲存此儀表板嗎?" }, - "save-and-overwrite": "「儲存並覆寫」" + "save-and-overwrite": "" }, "library-viz-panel-info": { "last-edited": "", @@ -7142,6 +7172,9 @@ "time-range-label": "鎖定時間範圍" } }, + "embedded-panel": { + "powered-by": "" + }, "empty-list-cta": { "pro-tip": "專業提示:{{proTip}}" }, @@ -7616,39 +7649,6 @@ }, "share-span": "分享" }, - "span-filters": { - "aria-label-select-max-span-operator": "選擇最大範圍的運算子", - "aria-label-select-min-span-operator": "選取最小範圍的運算子", - "aria-label-select-service-name": "選取服務名稱", - "aria-label-select-service-name-operator": "選取服務名稱運算子", - "aria-label-select-span-name": "選取範圍名稱", - "aria-label-select-span-name-operator": "選擇範圍名稱運算子", - "ariaLabel-select-max-span-duration": "選擇最大跨度持續時間", - "ariaLabel-select-min-span-duration": "選取最小跨度持續時間", - "label-collapse": "範圍篩選器", - "label-duration": "持續時間", - "label-service-name": "服務名稱", - "label-span-name": "範圍名稱", - "label-tags": "標籤", - "placeholder-all-service-names": "所有服務名稱", - "placeholder-all-span-names": "所有範圍名稱", - "tooltip-collapse": "篩選下方的範圍。您可以繼續套用篩選條件,直到將結果範圍縮小到您最感興趣的幾個範圍。", - "tooltip-duration": "依持續時間篩選。可接受的單元為 {{units}}", - "tooltip-tags": "按範圍中的標記、流程標記或紀錄欄位篩選。" - }, - "span-filters-tags": { - "aria-label-add-tag": "新增標記", - "aria-label-input-tag-value": "輸入標記值", - "aria-label-remove-tag": "移除標記", - "aria-label-select-tag-key": "選擇標記鍵", - "aria-label-select-tag-operator": "選擇標記運算子", - "aria-label-select-tag-value": "選擇標記值", - "placeholder-select-tag": "選擇標記", - "placeholder-select-value": "選擇值", - "placeholder-tag-value": "標記值", - "tooltip-add-tag": "新增標記", - "tooltip-remove-tag": "移除標記" - }, "span-flame-graph": { "flame-graph": "火焰圖" }, @@ -7847,23 +7847,18 @@ "label-upsample": "向上取樣", "tooltip-s-m-h": "10 秒、1 分鐘、30 分鐘、1 小時" }, - "schema-inspector-panel": { - "aria-label-close-schema-inspector": "" - }, "sql-expr": { "button-run-query": "執行查詢", - "modal-title": "SQL 編輯器", "tooltip-experimental": "SQL 運算式 LLM 整合為實驗性功能。如有任何問題,請向 Grafana 團隊回報。" }, "sql-schema": { - "close-schema-inspector": "", "error-title": "", - "inspect-button": "", "loading": "", "no-data-title": "", "no-fields-desc": "", "no-fields-title": "", - "query-error-title": "" + "query-error-title": "", + "schema-inspector": "" }, "threshold": { "label-input": "輸入" @@ -7938,11 +7933,7 @@ "suggestions": { "arc": "", "circular": "", - "no-thresholds": "", - "style": { - "circular": "", - "simple": "" - } + "no-thresholds": "" }, "threshold": "" }, @@ -9358,6 +9349,7 @@ "name-unit": "單位", "name-value-name": "值名稱", "name-y-axis-scale": "Y 軸刻度", + "name-y-bucket-scale": "", "placeholder-axis-label": "自動", "placeholder-axis-width": "自動", "placeholder-decimals": "自動", @@ -9387,6 +9379,18 @@ "label-all": "全部", "label-hidden": "隱藏", "label-single": "單一" + }, + "y-bucket-scale-editor": { + "linear-threshold-description": "", + "linear-threshold-label": "", + "linear-threshold-placeholder": "", + "log-base-label": "", + "scale-options": { + "label-auto": "", + "label-linear": "", + "label-log": "", + "label-symlog": "" + } } }, "help-modal": { @@ -11157,9 +11161,14 @@ }, "visualization-suggestions": { "apply-suggestion-aria-label": "", + "error-loading-some-suggestions": { + "message": "" + }, "error-loading-suggestions": { "message": "", - "title": "" + "plugin-failed": "", + "title": "", + "try-again-button": "" }, "unknown-viz-type": "", "use-this-suggestion": "" @@ -11921,7 +11930,10 @@ "resource-not-found": "", "unsupported-repository-type": "" }, - "inline-secure-values-warning": "", + "instance-sync-deprecation": { + "message": "", + "title": "" + }, "job-status": { "label-view-details": "檢視詳細資料", "loading-finished-job": "正在載入已完成的作業…", @@ -12027,6 +12039,9 @@ "webhook-last-event": "上次事件:", "webhook-url": "檢視 Webhook" }, + "repository-status": { + "error": "" + }, "repository-status-page": { "back-to-repositories": "返回至儲存庫", "cleaning-up-resources": "清理儲存庫資源", @@ -12131,6 +12146,9 @@ "tooltip-unhealthy-repository": "無法拉取狀態不佳的儲存庫" }, "synchronize-step": { + "instance-migrate-resources-description": "", + "migrate-resources-description": "", + "options": "", "repository-error": "", "repository-error-message": "", "repository-unhealthy": "" @@ -12150,10 +12168,15 @@ }, "warning-title-default": "警告", "wizard": { + "alert-intro": "", "alert-point-1": "", - "alert-point-2": "", "alert-point-3": "此流程的持續時間取決於所涉及的資源數量。", "alert-point-4": "", + "alert-point-folder-cleanup": "", + "alert-point-folder-structure": "", + "alert-point-instance-alerts": "", + "alert-point-permissions": "", + "alert-point-unsupported": "", "alert-title": "", "button-cancel": "", "button-cancelling": "", @@ -12172,6 +12195,7 @@ "step-finish": "選擇附加設定", "step-synchronize": "與外部儲存空間同步", "sync-description": "將資源與外部儲存空間同步。在此一次性步驟之後,未來的所有更新都將自動儲存到儲存庫中,並佈建回執行個體。", + "sync-option-migrate-resources": "", "title-bootstrap": "選擇要同步的內容", "title-connect": "連接到外部儲存空間", "title-finish": "選擇附加設定", @@ -12408,16 +12432,21 @@ }, "radialbar": { "config": { + "bar-shape": "", + "bar-shape-flat": "", + "bar-shape-rounded": "", "bar-width": "", "effects": { "bar-glow": "", "center-glow": "", "gradient": "", - "label": "", - "rounded-bars": "", - "spotlight": "", - "spotlight-tooltip": "" + "label": "" }, + "endpoint-marker": "", + "endpoint-marker-description": "", + "endpoint-marker-glow": "", + "endpoint-marker-none": "", + "endpoint-marker-point": "", "segment-count": "", "segment-spacing": "", "shape": "", From 15b5dcda806bbea6e01d7fdc056c3af5facd0eb9 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Mon, 22 Dec 2025 04:00:37 -0700 Subject: [PATCH 276/297] Dashboard V1->V2 Conversion: Default multi to true in GroupBy when multi is not defined in v1 (#115656) default multi to true when multi is not defined in v1 --- .../testdata/input/v1beta1.groupby.json | 166 +++++++++++++ .../output/v1beta1.groupby.v0alpha1.json | 172 +++++++++++++ .../output/v1beta1.groupby.v2alpha1.json | 229 +++++++++++++++++ .../output/v1beta1.groupby.v2beta1.json | 232 ++++++++++++++++++ .../conversion/v1beta1_to_v2alpha1.go | 4 +- 5 files changed, 802 insertions(+), 1 deletion(-) create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.groupby.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v0alpha1.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v2alpha1.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v2beta1.json diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.groupby.json b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.groupby.json new file mode 100644 index 00000000000..88527ef6036 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.groupby.json @@ -0,0 +1,166 @@ +{ + "kind": "DashboardWithAccessInfo", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "groupby-test" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "test-uid" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-pre", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "test-uid" + }, + "editorMode": "code", + "expr": "sum(counters_requests)", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "works with group by var", + "type": "timeseries" + } + ], + "preload": false, + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "current": { + "text": [ + "a_legacy_label", + "app", + "exported_instance", + "exported_job" + ], + "value": [ + "a_legacy_label", + "app", + "exported_instance", + "exported_job" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "test-uid" + }, + "name": "Group by", + "type": "groupby" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "groupby test", + "weekStart": "" + } + } \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v0alpha1.json new file mode 100644 index 00000000000..463d1864dce --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v0alpha1.json @@ -0,0 +1,172 @@ +{ + "kind": "DashboardWithAccessInfo", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "groupby-test" + }, + "spec": { + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "test-uid" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0-pre", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "test-uid" + }, + "editorMode": "code", + "expr": "sum(counters_requests)", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "works with group by var", + "type": "timeseries" + } + ], + "preload": false, + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "current": { + "text": [ + "a_legacy_label", + "app", + "exported_instance", + "exported_job" + ], + "value": [ + "a_legacy_label", + "app", + "exported_instance", + "exported_job" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "test-uid" + }, + "name": "Group by", + "type": "groupby" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "groupby test", + "weekStart": "" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v2alpha1.json new file mode 100644 index 00000000000..58bf555354c --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v2alpha1.json @@ -0,0 +1,229 @@ +{ + "kind": "DashboardWithAccessInfo", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "groupby-test" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "query": { + "kind": "grafana", + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "works with group by var", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "prometheus", + "spec": { + "editorMode": "code", + "expr": "sum(counters_requests)", + "legendFormat": "__auto", + "range": true + } + }, + "datasource": { + "type": "prometheus", + "uid": "test-uid" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "12.4.0-pre", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "browser", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "groupby test", + "variables": [ + { + "kind": "GroupByVariable", + "spec": { + "name": "Group by", + "datasource": { + "type": "prometheus", + "uid": "test-uid" + }, + "current": { + "text": [ + "a_legacy_label", + "app", + "exported_instance", + "exported_job" + ], + "value": [ + "a_legacy_label", + "app", + "exported_instance", + "exported_job" + ] + }, + "options": [], + "multi": true, + "hide": "dontHide", + "skipUrlSync": false + } + } + ] + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v2beta1.json new file mode 100644 index 00000000000..c2ac16a874c --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.groupby.v2beta1.json @@ -0,0 +1,232 @@ +{ + "kind": "DashboardWithAccessInfo", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "groupby-test" + }, + "spec": { + "annotations": [ + { + "kind": "AnnotationQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "grafana", + "version": "v0", + "datasource": { + "name": "-- Grafana --" + }, + "spec": {} + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations \u0026 Alerts", + "builtIn": true, + "legacyOptions": { + "type": "dashboard" + } + } + } + ], + "cursorSync": "Off", + "editable": true, + "elements": { + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "works with group by var", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "test-uid" + }, + "spec": { + "editorMode": "code", + "expr": "sum(counters_requests)", + "legendFormat": "__auto", + "range": true + } + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "12.4.0-pre", + "spec": { + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": 0, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + }, + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + } + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [], + "liveNow": false, + "preload": false, + "tags": [], + "timeSettings": { + "timezone": "browser", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "groupby test", + "variables": [ + { + "kind": "GroupByVariable", + "group": "prometheus", + "datasource": { + "name": "test-uid" + }, + "spec": { + "name": "Group by", + "current": { + "text": [ + "a_legacy_label", + "app", + "exported_instance", + "exported_job" + ], + "value": [ + "a_legacy_label", + "app", + "exported_instance", + "exported_job" + ] + }, + "options": [], + "multi": true, + "hide": "dontHide", + "skipUrlSync": false + } + } + ] + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go index 92c5d937fe7..224f222ae33 100644 --- a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go +++ b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go @@ -1734,7 +1734,9 @@ func buildGroupByVariable(ctx context.Context, varMap map[string]interface{}, co Hide: commonProps.Hide, SkipUrlSync: commonProps.SkipUrlSync, Current: buildVariableCurrent(varMap["current"]), - Multi: getBoolField(varMap, "multi", false), + // We set it to true by default because GroupByVariable + // constructor defaults to multi: true + Multi: getBoolField(varMap, "multi", true), }, } From dd1edf7f16a6d9ff132002eea175a5c1765777e3 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Mon, 22 Dec 2025 21:23:59 +0100 Subject: [PATCH 277/297] Alerting: Fix database-based filtering by labels when rules have no labels (#115657) Alerting: Fix database-based filtering by labels when rules have no labels at all --- pkg/services/ngalert/store/alert_rule_labels_test.go | 12 ++++++------ pkg/services/ngalert/store/alert_rule_test.go | 12 +++++++++--- pkg/services/ngalert/store/json.go | 12 ++++++------ pkg/services/ngalert/store/json_test.go | 12 ++++++------ 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/pkg/services/ngalert/store/alert_rule_labels_test.go b/pkg/services/ngalert/store/alert_rule_labels_test.go index 9b72d8f00f9..2006c49fd7a 100644 --- a/pkg/services/ngalert/store/alert_rule_labels_test.go +++ b/pkg/services/ngalert/store/alert_rule_labels_test.go @@ -73,42 +73,42 @@ func TestBuildLabelMatcherJSON(t *testing.T) { name: "MySQL MatchEqual with non-empty value", dialect: migrator.NewMysqlDialect(), matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: "alerting"}, - wantSQL: "JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) = ?", + wantSQL: "JSON_UNQUOTE(JSON_EXTRACT(NULLIF(labels, ''), CONCAT('$.', ?))) = ?", wantArgs: []any{"team", "alerting"}, }, { name: "MySQL MatchEqual with empty value", dialect: migrator.NewMysqlDialect(), matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: ""}, - wantSQL: "(JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) = ? OR JSON_EXTRACT(labels, CONCAT('$.', ?)) IS NULL)", + wantSQL: "(JSON_UNQUOTE(JSON_EXTRACT(NULLIF(labels, ''), CONCAT('$.', ?))) = ? OR JSON_EXTRACT(NULLIF(labels, ''), CONCAT('$.', ?)) IS NULL)", wantArgs: []any{"team", "", "team"}, }, { name: "MySQL MatchNotEqual", dialect: migrator.NewMysqlDialect(), matcher: &labels.Matcher{Type: labels.MatchNotEqual, Name: "team", Value: "alerting"}, - wantSQL: "(JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) IS NULL OR JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) != ?)", + wantSQL: "(JSON_UNQUOTE(JSON_EXTRACT(NULLIF(labels, ''), CONCAT('$.', ?))) IS NULL OR JSON_UNQUOTE(JSON_EXTRACT(NULLIF(labels, ''), CONCAT('$.', ?))) != ?)", wantArgs: []any{"team", "team", "alerting"}, }, { name: "PostgreSQL MatchEqual with non-empty value", dialect: migrator.NewPostgresDialect(), matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: "alerting"}, - wantSQL: "jsonb_extract_path_text(labels::jsonb, ?) = ?", + wantSQL: "jsonb_extract_path_text(NULLIF(labels, '')::jsonb, ?) = ?", wantArgs: []any{"team", "alerting"}, }, { name: "PostgreSQL MatchEqual with empty value", dialect: migrator.NewPostgresDialect(), matcher: &labels.Matcher{Type: labels.MatchEqual, Name: "team", Value: ""}, - wantSQL: "(jsonb_extract_path_text(labels::jsonb, ?) = ? OR jsonb_extract_path_text(labels::jsonb, ?) IS NULL)", + wantSQL: "(jsonb_extract_path_text(NULLIF(labels, '')::jsonb, ?) = ? OR jsonb_extract_path_text(NULLIF(labels, '')::jsonb, ?) IS NULL)", wantArgs: []any{"team", "", "team"}, }, { name: "PostgreSQL MatchNotEqual", dialect: migrator.NewPostgresDialect(), matcher: &labels.Matcher{Type: labels.MatchNotEqual, Name: "team", Value: "alerting"}, - wantSQL: "(jsonb_extract_path_text(labels::jsonb, ?) IS NULL OR jsonb_extract_path_text(labels::jsonb, ?) != ?)", + wantSQL: "(jsonb_extract_path_text(NULLIF(labels, '')::jsonb, ?) IS NULL OR jsonb_extract_path_text(NULLIF(labels, '')::jsonb, ?) != ?)", wantArgs: []any{"team", "team", "alerting"}, }, { diff --git a/pkg/services/ngalert/store/alert_rule_test.go b/pkg/services/ngalert/store/alert_rule_test.go index 29b82f943ba..f7497c7b05a 100644 --- a/pkg/services/ngalert/store/alert_rule_test.go +++ b/pkg/services/ngalert/store/alert_rule_test.go @@ -2462,6 +2462,12 @@ func TestIntegration_ListAlertRules(t *testing.T) { ruleNonempty := createRule(t, store, ruleGen.With( ruleGen.WithLabels(map[string]string{"empty": "nonempty"}), ruleGen.WithTitle("rule_nonempty"))) + // include a rule with no labels at all, + // to ensure we handle that case correctly. + // JSON functions need to be able to handle null and empty string values. + ruleNoLabels := createRule(t, store, ruleGen.With( + ruleGen.WithLabels(map[string]string{}), + ruleGen.WithTitle("rule_no_labels"))) tc := []struct { name string @@ -2487,7 +2493,7 @@ func TestIntegration_ListAlertRules(t *testing.T) { labelMatchers: labels.Matchers{ func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchNotEqual, "team", "alerting"); return m }(), }, - expectedRules: []*models.AlertRule{ruleUpper, ruleSpecial, ruleGlob, ruleSpecialChars, ruleEmpty, ruleNonempty}, + expectedRules: []*models.AlertRule{ruleUpper, ruleSpecial, ruleGlob, ruleSpecialChars, ruleEmpty, ruleNonempty, ruleNoLabels}, }, { name: "special characters in labels are handled correctly", @@ -2536,7 +2542,7 @@ func TestIntegration_ListAlertRules(t *testing.T) { labelMatchers: labels.Matchers{ func() *labels.Matcher { m, _ := labels.NewMatcher(labels.MatchEqual, "empty", ""); return m }(), }, - expectedRules: []*models.AlertRule{ruleLower, ruleUpper, ruleSpecial, ruleGlob, ruleSpecialChars, ruleEmpty}, + expectedRules: []*models.AlertRule{ruleLower, ruleUpper, ruleSpecial, ruleGlob, ruleSpecialChars, ruleEmpty, ruleNoLabels}, }, { name: "inequality matcher on non-existent label matches all rules", @@ -2546,7 +2552,7 @@ func TestIntegration_ListAlertRules(t *testing.T) { return m }(), }, - expectedRules: []*models.AlertRule{ruleLower, ruleUpper, ruleSpecial, ruleGlob, ruleSpecialChars, ruleEmpty, ruleNonempty}, + expectedRules: []*models.AlertRule{ruleLower, ruleUpper, ruleSpecial, ruleGlob, ruleSpecialChars, ruleEmpty, ruleNonempty, ruleNoLabels}, }, } diff --git a/pkg/services/ngalert/store/json.go b/pkg/services/ngalert/store/json.go index 7b634246951..e0c71975a76 100644 --- a/pkg/services/ngalert/store/json.go +++ b/pkg/services/ngalert/store/json.go @@ -13,9 +13,9 @@ import ( func jsonEquals(dialect migrator.Dialect, column, key, value string) (string, []any) { switch dialect.DriverName() { case migrator.MySQL: - return fmt.Sprintf("JSON_UNQUOTE(JSON_EXTRACT(%s, CONCAT('$.', ?))) = ?", column), []any{key, value} + return fmt.Sprintf("JSON_UNQUOTE(JSON_EXTRACT(NULLIF(%s, ''), CONCAT('$.', ?))) = ?", column), []any{key, value} case migrator.Postgres: - return fmt.Sprintf("jsonb_extract_path_text(%s::jsonb, ?) = ?", column), []any{key, value} + return fmt.Sprintf("jsonb_extract_path_text(NULLIF(%s, '')::jsonb, ?) = ?", column), []any{key, value} default: return "", nil } @@ -25,9 +25,9 @@ func jsonNotEquals(dialect migrator.Dialect, column, key, value string) (string, var jx string switch dialect.DriverName() { case migrator.MySQL: - jx = fmt.Sprintf("JSON_UNQUOTE(JSON_EXTRACT(%s, CONCAT('$.', ?)))", column) + jx = fmt.Sprintf("JSON_UNQUOTE(JSON_EXTRACT(NULLIF(%s, ''), CONCAT('$.', ?)))", column) case migrator.Postgres: - jx = fmt.Sprintf("jsonb_extract_path_text(%s::jsonb, ?)", column) + jx = fmt.Sprintf("jsonb_extract_path_text(NULLIF(%s, '')::jsonb, ?)", column) default: return "", nil } @@ -37,9 +37,9 @@ func jsonNotEquals(dialect migrator.Dialect, column, key, value string) (string, func jsonKeyMissing(dialect migrator.Dialect, column, key string) (string, []any) { switch dialect.DriverName() { case migrator.MySQL: - return fmt.Sprintf("JSON_EXTRACT(%s, CONCAT('$.', ?)) IS NULL", column), []any{key} + return fmt.Sprintf("JSON_EXTRACT(NULLIF(%s, ''), CONCAT('$.', ?)) IS NULL", column), []any{key} case migrator.Postgres: - return fmt.Sprintf("jsonb_extract_path_text(%s::jsonb, ?) IS NULL", column), []any{key} + return fmt.Sprintf("jsonb_extract_path_text(NULLIF(%s, '')::jsonb, ?) IS NULL", column), []any{key} default: return "", nil } diff --git a/pkg/services/ngalert/store/json_test.go b/pkg/services/ngalert/store/json_test.go index 89f85a027a6..d09d3741c4b 100644 --- a/pkg/services/ngalert/store/json_test.go +++ b/pkg/services/ngalert/store/json_test.go @@ -23,7 +23,7 @@ func TestJsonEquals(t *testing.T) { column: "labels", key: "team", value: "alerting", - wantSQL: "JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) = ?", + wantSQL: "JSON_UNQUOTE(JSON_EXTRACT(NULLIF(labels, ''), CONCAT('$.', ?))) = ?", wantArgs: []any{"team", "alerting"}, }, { @@ -32,7 +32,7 @@ func TestJsonEquals(t *testing.T) { column: "labels", key: "team", value: "alerting", - wantSQL: "jsonb_extract_path_text(labels::jsonb, ?) = ?", + wantSQL: "jsonb_extract_path_text(NULLIF(labels, '')::jsonb, ?) = ?", wantArgs: []any{"team", "alerting"}, }, } @@ -62,7 +62,7 @@ func TestJsonNotEquals(t *testing.T) { column: "labels", key: "team", value: "alerting", - wantSQL: "(JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) IS NULL OR JSON_UNQUOTE(JSON_EXTRACT(labels, CONCAT('$.', ?))) != ?)", + wantSQL: "(JSON_UNQUOTE(JSON_EXTRACT(NULLIF(labels, ''), CONCAT('$.', ?))) IS NULL OR JSON_UNQUOTE(JSON_EXTRACT(NULLIF(labels, ''), CONCAT('$.', ?))) != ?)", wantArgs: []any{"team", "team", "alerting"}, }, { @@ -71,7 +71,7 @@ func TestJsonNotEquals(t *testing.T) { column: "labels", key: "team", value: "alerting", - wantSQL: "(jsonb_extract_path_text(labels::jsonb, ?) IS NULL OR jsonb_extract_path_text(labels::jsonb, ?) != ?)", + wantSQL: "(jsonb_extract_path_text(NULLIF(labels, '')::jsonb, ?) IS NULL OR jsonb_extract_path_text(NULLIF(labels, '')::jsonb, ?) != ?)", wantArgs: []any{"team", "team", "alerting"}, }, } @@ -99,7 +99,7 @@ func TestJsonKeyMissing(t *testing.T) { dialect: migrator.NewMysqlDialect(), column: "labels", key: "team", - wantSQL: "JSON_EXTRACT(labels, CONCAT('$.', ?)) IS NULL", + wantSQL: "JSON_EXTRACT(NULLIF(labels, ''), CONCAT('$.', ?)) IS NULL", wantArgs: []any{"team"}, }, { @@ -107,7 +107,7 @@ func TestJsonKeyMissing(t *testing.T) { dialect: migrator.NewPostgresDialect(), column: "labels", key: "team", - wantSQL: "jsonb_extract_path_text(labels::jsonb, ?) IS NULL", + wantSQL: "jsonb_extract_path_text(NULLIF(labels, '')::jsonb, ?) IS NULL", wantArgs: []any{"team"}, }, } From 096208202ebab02f8842ae08803b1df724dcedfc Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Tue, 23 Dec 2025 11:23:16 +0100 Subject: [PATCH 278/297] Alerting: Fix a race condition panic in ResetStateByRuleUID (#115662) --- pkg/services/ngalert/schedule/registry.go | 16 ++++++----- pkg/services/ngalert/state/manager.go | 10 ++++--- pkg/services/ngalert/state/manager_test.go | 33 ++++++++++++++++++++++ 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/pkg/services/ngalert/schedule/registry.go b/pkg/services/ngalert/schedule/registry.go index 892e0af8235..6ce09d49169 100644 --- a/pkg/services/ngalert/schedule/registry.go +++ b/pkg/services/ngalert/schedule/registry.go @@ -101,13 +101,13 @@ func (e *Evaluation) Fingerprint() fingerprint { type alertRulesRegistry struct { rules map[models.AlertRuleKey]*models.AlertRule folderTitles map[models.FolderKey]string - mu sync.Mutex + mu sync.RWMutex } // all returns all rules in the registry. func (r *alertRulesRegistry) all() ([]*models.AlertRule, map[models.FolderKey]string) { - r.mu.Lock() - defer r.mu.Unlock() + r.mu.RLock() + defer r.mu.RUnlock() result := make([]*models.AlertRule, 0, len(r.rules)) for _, rule := range r.rules { result = append(result, rule) @@ -116,8 +116,8 @@ func (r *alertRulesRegistry) all() ([]*models.AlertRule, map[models.FolderKey]st } func (r *alertRulesRegistry) get(k models.AlertRuleKey) *models.AlertRule { - r.mu.Lock() - defer r.mu.Unlock() + r.mu.RLock() + defer r.mu.RUnlock() return r.rules[k] } @@ -157,12 +157,14 @@ func (r *alertRulesRegistry) del(k models.AlertRuleKey) (*models.AlertRule, bool } func (r *alertRulesRegistry) isEmpty() bool { - r.mu.Lock() - defer r.mu.Unlock() + r.mu.RLock() + defer r.mu.RUnlock() return len(r.rules) == 0 } func (r *alertRulesRegistry) needsUpdate(keys []models.AlertRuleKeyWithVersion) bool { + r.mu.RLock() + defer r.mu.RUnlock() if len(r.rules) != len(keys) { return true } diff --git a/pkg/services/ngalert/state/manager.go b/pkg/services/ngalert/state/manager.go index 4d6e066f2bf..45c29f9d684 100644 --- a/pkg/services/ngalert/state/manager.go +++ b/pkg/services/ngalert/state/manager.go @@ -232,9 +232,7 @@ func (st *Manager) Get(orgID int64, alertRuleUID string, stateId data.Fingerprin return st.cache.get(orgID, alertRuleUID, stateId) } -// DeleteStateByRuleUID removes the rule instances from cache and instanceStore. A closed channel is returned to be able -// to gracefully handle the clear state step in scheduler in case we do not need to use the historian to save state -// history. +// DeleteStateByRuleUID removes the rule instances from cache and instanceStore. func (st *Manager) DeleteStateByRuleUID(ctx context.Context, ruleKey ngModels.AlertRuleKeyWithGroup, reason string) []StateTransition { logger := st.log.FromContext(ctx) logger.Debug("Resetting state of the rule") @@ -292,10 +290,14 @@ func (st *Manager) ForgetStateByRuleUID(ctx context.Context, ruleKey ngModels.Al // ResetStateByRuleUID removes the rule instances from cache and instanceStore and saves state history. If the state // history has to be saved, rule must not be nil. func (st *Manager) ResetStateByRuleUID(ctx context.Context, rule *ngModels.AlertRule, reason string) []StateTransition { + if rule == nil { + return nil + } + ruleKey := rule.GetKeyWithGroup() transitions := st.DeleteStateByRuleUID(ctx, ruleKey, reason) - if rule == nil || st.historian == nil || len(transitions) == 0 { + if st.historian == nil || len(transitions) == 0 { return transitions } diff --git a/pkg/services/ngalert/state/manager_test.go b/pkg/services/ngalert/state/manager_test.go index 9dccbe52d2d..b8b366ac619 100644 --- a/pkg/services/ngalert/state/manager_test.go +++ b/pkg/services/ngalert/state/manager_test.go @@ -2051,6 +2051,39 @@ func TestIntegrationResetStateByRuleUID(t *testing.T) { } } +func TestResetStateByRuleUID(t *testing.T) { + ctx := context.Background() + + setupManager := func(historian state.Historian) *state.Manager { + cfg := state.ManagerCfg{ + Metrics: metrics.NewNGAlert(prometheus.NewPedanticRegistry()).GetStateMetrics(), + ExternalURL: nil, + InstanceStore: &state.FakeInstanceStore{}, + Images: &state.NoopImageService{}, + Clock: clock.NewMock(), + Historian: historian, + Tracer: tracing.InitializeTracerForTest(), + Log: log.New("ngalert.state.manager"), + } + + return state.NewManager(cfg, state.NewNoopPersister()) + } + + t.Run("with nil historian", func(t *testing.T) { + manager := setupManager(nil) + + transitions := manager.ResetStateByRuleUID(ctx, nil, "test reason") + require.Empty(t, transitions) + }) + + t.Run("with historian", func(t *testing.T) { + manager := setupManager(&state.FakeHistorian{}) + + transitions := manager.ResetStateByRuleUID(ctx, nil, "test reason") + require.Empty(t, transitions) + }) +} + func setCacheID(s *state.State) *state.State { if s.CacheID != 0 { return s From 84120fb2107267701ba3f4f22ccb24fac943e9c9 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Tue, 23 Dec 2025 11:26:15 +0100 Subject: [PATCH 279/297] Alerting: Fix file import/export of recording rules with target datasource uid (#115663) Alerting: Fix export of recording rules with target datasource uid --- .../test-data/post-rulegroup-101-export.hcl | 45 ++++++++++ .../test-data/post-rulegroup-101-export.json | 61 ++++++++++++++ .../test-data/post-rulegroup-101-export.yaml | 45 ++++++++++ .../api/test-data/post-rulegroup-101.json | 65 +++++++++++++++ .../alerting/config_reader_test.go | 40 +++++++++ .../provisioning/alerting/rules_types.go | 10 ++- .../provisioning/alerting/rules_types_test.go | 82 ++++++------------- .../with-target-datasource.yml | 26 ++++++ .../without-target-datasource.yml | 25 ++++++ .../test-data/rulegroup-1-export.json | 27 ++++++ .../alerting/test-data/rulegroup-1-get.json | 38 +++++++++ .../alerting/test-data/rulegroup-1-post.json | 20 +++++ pkg/tests/api/alerting/testing.go | 1 + 13 files changed, 426 insertions(+), 59 deletions(-) create mode 100644 pkg/services/provisioning/alerting/testdata/alert_rules/recording-rules/with-target-datasource.yml create mode 100644 pkg/services/provisioning/alerting/testdata/alert_rules/recording-rules/without-target-datasource.yml diff --git a/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.hcl b/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.hcl index 3a5882e0a49..5daa4dce406 100644 --- a/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.hcl +++ b/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.hcl @@ -173,4 +173,49 @@ resource "grafana_rule_group" "rule_group_d3e8424bfbf66bc3" { from = "condition" } } + rule { + name = "recording rule with target" + + data { + ref_id = "query" + + relative_time_range { + from = 18000 + to = 10800 + } + + datasource_uid = "000000002" + model = "{\"expr\":\"rate(http_requests_total[5m])\",\"hide\":false,\"interval\":\"\",\"intervalMs\":1000,\"legendFormat\":\"\",\"maxDataPoints\":100,\"refId\":\"query\"}" + } + data { + ref_id = "reduced" + + relative_time_range { + from = 18000 + to = 10800 + } + + datasource_uid = "__expr__" + model = "{\"expression\":\"query\",\"hide\":false,\"intervalMs\":1000,\"maxDataPoints\":100,\"reducer\":\"mean\",\"refId\":\"reduced\",\"type\":\"reduce\"}" + } + data { + ref_id = "condition" + + relative_time_range { + from = 18000 + to = 10800 + } + + datasource_uid = "__expr__" + model = "{\"expression\":\"$reduced > 5\",\"hide\":false,\"intervalMs\":1000,\"maxDataPoints\":100,\"refId\":\"condition\",\"type\":\"math\"}" + } + + is_paused = false + + record { + metric = "http_requests_rate" + from = "condition" + target_datasource_uid = "000000003" + } + } } diff --git a/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.json b/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.json index bde4bb31f57..4fe9a2dc75a 100644 --- a/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.json +++ b/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.json @@ -233,6 +233,67 @@ "metric": "test_metric", "from": "condition" } + }, + { + "title": "recording rule with target", + "data": [ + { + "refId": "query", + "relativeTimeRange": { + "from": 18000, + "to": 10800 + }, + "datasourceUid": "000000002", + "model": { + "expr": "rate(http_requests_total[5m])", + "hide": false, + "interval": "", + "intervalMs": 1000, + "legendFormat": "", + "maxDataPoints": 100, + "refId": "query" + } + }, + { + "refId": "reduced", + "relativeTimeRange": { + "from": 18000, + "to": 10800 + }, + "datasourceUid": "__expr__", + "model": { + "expression": "query", + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 100, + "reducer": "mean", + "refId": "reduced", + "type": "reduce" + } + }, + { + "refId": "condition", + "relativeTimeRange": { + "from": 18000, + "to": 10800 + }, + "datasourceUid": "__expr__", + "model": { + "expression": "$reduced \u003e 5", + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 100, + "refId": "condition", + "type": "math" + } + } + ], + "isPaused": false, + "record": { + "metric": "http_requests_rate", + "from": "condition", + "targetDatasourceUid": "000000003" + } } ] } diff --git a/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.yaml b/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.yaml index bdb6e9e8362..242a2204823 100644 --- a/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.yaml +++ b/pkg/services/ngalert/api/test-data/post-rulegroup-101-export.yaml @@ -187,3 +187,48 @@ groups: record: metric: test_metric from: condition + - title: recording rule with target + data: + - refId: query + relativeTimeRange: + from: 18000 + to: 10800 + datasourceUid: "000000002" + model: + expr: rate(http_requests_total[5m]) + hide: false + interval: "" + intervalMs: 1000 + legendFormat: "" + maxDataPoints: 100 + refId: query + - refId: reduced + relativeTimeRange: + from: 18000 + to: 10800 + datasourceUid: __expr__ + model: + expression: query + hide: false + intervalMs: 1000 + maxDataPoints: 100 + reducer: mean + refId: reduced + type: reduce + - refId: condition + relativeTimeRange: + from: 18000 + to: 10800 + datasourceUid: __expr__ + model: + expression: $reduced > 5 + hide: false + intervalMs: 1000 + maxDataPoints: 100 + refId: condition + type: math + isPaused: false + record: + metric: http_requests_rate + from: condition + targetDatasourceUid: "000000003" diff --git a/pkg/services/ngalert/api/test-data/post-rulegroup-101.json b/pkg/services/ngalert/api/test-data/post-rulegroup-101.json index f0eaa36d40d..e9936041979 100644 --- a/pkg/services/ngalert/api/test-data/post-rulegroup-101.json +++ b/pkg/services/ngalert/api/test-data/post-rulegroup-101.json @@ -240,6 +240,71 @@ "from": "condition" } } + }, + { + "grafana_alert": { + "title": "recording rule with target", + "data": [ + { + "refId": "query", + "queryType": "", + "relativeTimeRange": { + "from": 18000, + "to": 10800 + }, + "datasourceUid": "000000002", + "model": { + "expr": "rate(http_requests_total[5m])", + "hide": false, + "interval": "", + "intervalMs": 1000, + "legendFormat": "", + "maxDataPoints": 100, + "refId": "query" + } + }, + { + "refId": "reduced", + "queryType": "", + "relativeTimeRange": { + "from": 18000, + "to": 10800 + }, + "datasourceUid": "__expr__", + "model": { + "expression": "query", + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 100, + "reducer": "mean", + "refId": "reduced", + "type": "reduce" + } + }, + { + "refId": "condition", + "queryType": "", + "relativeTimeRange": { + "from": 18000, + "to": 10800 + }, + "datasourceUid": "__expr__", + "model": { + "expression": "$reduced > 5", + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 100, + "refId": "condition", + "type": "math" + } + } + ], + "record": { + "metric": "http_requests_rate", + "from": "condition", + "target_datasource_uid": "000000003" + } + } } ] } diff --git a/pkg/services/provisioning/alerting/config_reader_test.go b/pkg/services/provisioning/alerting/config_reader_test.go index c13780633c3..9cb71b4950f 100644 --- a/pkg/services/provisioning/alerting/config_reader_test.go +++ b/pkg/services/provisioning/alerting/config_reader_test.go @@ -19,6 +19,7 @@ const ( testFileDasboardTypoSupport = "./testdata/alert_rules/dasboard-typo-support" testFileMultipleRules = "./testdata/alert_rules/multiple-rules" testFileMultipleFiles = "./testdata/alert_rules/multiple-files" + testFileRecordingRules = "./testdata/alert_rules/recording-rules" testFileCorrectProperties_cp = "./testdata/contact_points/correct-properties" testFileCorrectPropertiesWithOrg_cp = "./testdata/contact_points/correct-properties-with-org" testFileEmptyUID = "./testdata/contact_points/empty-uid" @@ -188,4 +189,43 @@ func TestConfigReader(t *testing.T) { } }) }) + + t.Run("recording rules should parse correctly", func(t *testing.T) { + ruleFiles, err := configReader.readConfig(ctx, testFileRecordingRules) + require.NoError(t, err) + require.Len(t, ruleFiles, 2) + + findRule := func(title string) *AlertingFile { + for _, rf := range ruleFiles { + if rf.Groups[0].Title == title { + return rf + } + } + return nil + } + + ruleWithTarget := findRule("recording_rules_group") + require.NotNil(t, ruleWithTarget) + + require.Len(t, ruleWithTarget.Groups, 1) + require.Len(t, ruleWithTarget.Groups[0].Rules, 1) + + ruleWith := ruleWithTarget.Groups[0].Rules[0] + require.NotNil(t, ruleWith.Record) + require.Equal(t, "my_recorded_metric", ruleWith.Record.Metric) + require.Equal(t, "A", ruleWith.Record.From) + require.Equal(t, "mimir-uid", ruleWith.Record.TargetDatasourceUID) + + ruleWithoutTarget := findRule("recording_rules_group_no_target") + require.NotNil(t, ruleWithoutTarget) + + require.Len(t, ruleWithoutTarget.Groups, 1) + require.Len(t, ruleWithoutTarget.Groups[0].Rules, 1) + + ruleWithout := ruleWithoutTarget.Groups[0].Rules[0] + require.NotNil(t, ruleWithout.Record) + require.Equal(t, "http_requests_rate", ruleWithout.Record.Metric) + require.Equal(t, "A", ruleWithout.Record.From) + require.Equal(t, "", ruleWithout.Record.TargetDatasourceUID) + }) } diff --git a/pkg/services/provisioning/alerting/rules_types.go b/pkg/services/provisioning/alerting/rules_types.go index bac091ababa..46dbbbd6342 100644 --- a/pkg/services/provisioning/alerting/rules_types.go +++ b/pkg/services/provisioning/alerting/rules_types.go @@ -303,13 +303,15 @@ func (nsV1 *NotificationSettingsV1) mapToModel() (models.NotificationSettings, e } type RecordV1 struct { - Metric values.StringValue `json:"metric" yaml:"metric"` - From values.StringValue `json:"from" yaml:"from"` + Metric values.StringValue `json:"metric" yaml:"metric"` + From values.StringValue `json:"from" yaml:"from"` + TargetDatasourceUID values.StringValue `json:"targetDatasourceUid" yaml:"targetDatasourceUid"` } func (record *RecordV1) mapToModel() (models.Record, error) { return models.Record{ - Metric: record.Metric.Value(), - From: record.From.Value(), + Metric: record.Metric.Value(), + From: record.From.Value(), + TargetDatasourceUID: record.TargetDatasourceUID.Value(), }, nil } diff --git a/pkg/services/provisioning/alerting/rules_types_test.go b/pkg/services/provisioning/alerting/rules_types_test.go index c2cff56e419..51db78097ad 100644 --- a/pkg/services/provisioning/alerting/rules_types_test.go +++ b/pkg/services/provisioning/alerting/rules_types_test.go @@ -208,6 +208,15 @@ func TestRecordingRules(t *testing.T) { _, err := rule.mapToModel(1) require.NoError(t, err) }) + + t.Run("a valid rule with empty targetDatasourceUid should not error", func(t *testing.T) { + rule := validRecordingRuleV1(t) + rule.Record.TargetDatasourceUID = stringToStringValue("") + model, err := rule.mapToModel(1) + require.NoError(t, err) + require.NotNil(t, model.Record) + require.Equal(t, "", model.Record.TargetDatasourceUID) + }) } func TestNotificationsSettingsV1MapToModel(t *testing.T) { @@ -307,80 +316,43 @@ func TestNotificationsSettingsV1MapToModel(t *testing.T) { func validRuleGroupV1(t *testing.T) AlertRuleGroupV1 { t.Helper() - var ( - orgID values.Int64Value - name values.StringValue - folder values.StringValue - interval values.StringValue - ) + + var orgID values.Int64Value err := yaml.Unmarshal([]byte("1"), &orgID) require.NoError(t, err) - err = yaml.Unmarshal([]byte("Test"), &name) - require.NoError(t, err) - err = yaml.Unmarshal([]byte("Test"), &folder) - require.NoError(t, err) - err = yaml.Unmarshal([]byte("10s"), &interval) - require.NoError(t, err) + return AlertRuleGroupV1{ OrgID: orgID, - Name: name, - Folder: folder, - Interval: interval, + Name: stringToStringValue("Test"), + Folder: stringToStringValue("Test"), + Interval: stringToStringValue("10s"), Rules: []AlertRuleV1{}, } } func validRuleV1(t *testing.T) AlertRuleV1 { t.Helper() - var ( - title values.StringValue - uid values.StringValue - forDuration values.StringValue - condition values.StringValue - ) - err := yaml.Unmarshal([]byte("test"), &title) - require.NoError(t, err) - err = yaml.Unmarshal([]byte("test_uid"), &uid) - require.NoError(t, err) - err = yaml.Unmarshal([]byte("10s"), &forDuration) - require.NoError(t, err) - err = yaml.Unmarshal([]byte("A"), &condition) - require.NoError(t, err) + return AlertRuleV1{ - Title: title, - UID: uid, - For: forDuration, - Condition: condition, + Title: stringToStringValue("test"), + UID: stringToStringValue("test_uid"), + For: stringToStringValue("10s"), + Condition: stringToStringValue("A"), Data: []QueryV1{{}}, } } func validRecordingRuleV1(t *testing.T) AlertRuleV1 { t.Helper() - var ( - title values.StringValue - uid values.StringValue - forDuration values.StringValue - metric values.StringValue - from values.StringValue - ) - err := yaml.Unmarshal([]byte("test"), &title) - require.NoError(t, err) - err = yaml.Unmarshal([]byte("test_uid"), &uid) - require.NoError(t, err) - err = yaml.Unmarshal([]byte("10s"), &forDuration) - require.NoError(t, err) - err = yaml.Unmarshal([]byte("test_metric"), &metric) - require.NoError(t, err) - err = yaml.Unmarshal([]byte("A"), &from) - require.NoError(t, err) + return AlertRuleV1{ - Title: title, - UID: uid, - For: forDuration, + Title: stringToStringValue("test"), + UID: stringToStringValue("test_uid"), + For: stringToStringValue("10s"), Record: &RecordV1{ - Metric: metric, - From: from, + Metric: stringToStringValue("test_metric"), + From: stringToStringValue("A"), + TargetDatasourceUID: stringToStringValue("test_target_datasource"), }, Data: []QueryV1{{}}, } diff --git a/pkg/services/provisioning/alerting/testdata/alert_rules/recording-rules/with-target-datasource.yml b/pkg/services/provisioning/alerting/testdata/alert_rules/recording-rules/with-target-datasource.yml new file mode 100644 index 00000000000..582ddf730ae --- /dev/null +++ b/pkg/services/provisioning/alerting/testdata/alert_rules/recording-rules/with-target-datasource.yml @@ -0,0 +1,26 @@ +apiVersion: 1 +groups: + - name: recording_rules_group + folder: my_folder + interval: 1m + rules: + - uid: recording_rule_with_target + title: my_recording_rule_with_target + condition: A + data: + - refId: A + queryType: '' + relativeTimeRange: + from: 600 + to: 0 + datasourceUid: prometheus-uid + model: + expr: up{instance="localhost:9090"} + instant: true + intervalMs: 1000 + maxDataPoints: 43200 + refId: A + record: + metric: my_recorded_metric + from: A + targetDatasourceUid: mimir-uid diff --git a/pkg/services/provisioning/alerting/testdata/alert_rules/recording-rules/without-target-datasource.yml b/pkg/services/provisioning/alerting/testdata/alert_rules/recording-rules/without-target-datasource.yml new file mode 100644 index 00000000000..0b8b2760988 --- /dev/null +++ b/pkg/services/provisioning/alerting/testdata/alert_rules/recording-rules/without-target-datasource.yml @@ -0,0 +1,25 @@ +apiVersion: 1 +groups: + - name: recording_rules_group_no_target + folder: my_folder + interval: 1m + rules: + - uid: recording_rule_without_target + title: my_recording_rule_without_target + condition: A + data: + - refId: A + queryType: '' + relativeTimeRange: + from: 600 + to: 0 + datasourceUid: prometheus-uid + model: + expr: rate(http_requests_total[5m]) + instant: true + intervalMs: 1000 + maxDataPoints: 43200 + refId: A + record: + metric: http_requests_rate + from: A diff --git a/pkg/tests/api/alerting/test-data/rulegroup-1-export.json b/pkg/tests/api/alerting/test-data/rulegroup-1-export.json index 18d8b8cea40..f8c01a3d883 100644 --- a/pkg/tests/api/alerting/test-data/rulegroup-1-export.json +++ b/pkg/tests/api/alerting/test-data/rulegroup-1-export.json @@ -72,6 +72,33 @@ }, "isPaused": false, "missing_series_evals_to_resolve": 2 + }, + { + "uid": "", + "title": "RecordingRule1", + "data": [ + { + "refId": "A", + "relativeTimeRange": { + "from": 0, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "expression": "1 + 1", + "intervalMs": 1000, + "maxDataPoints": 43200, + "refId": "A", + "type": "math" + } + } + ], + "isPaused": false, + "record": { + "metric": "test_metric", + "from": "A", + "targetDatasourceUid": "test-datasource-uid" + } } ] } diff --git a/pkg/tests/api/alerting/test-data/rulegroup-1-get.json b/pkg/tests/api/alerting/test-data/rulegroup-1-get.json index 333bd32c0d1..d392254d756 100644 --- a/pkg/tests/api/alerting/test-data/rulegroup-1-get.json +++ b/pkg/tests/api/alerting/test-data/rulegroup-1-get.json @@ -97,6 +97,44 @@ }, "missing_series_evals_to_resolve": 2 } + }, + { + "expr": "", + "for": "0s", + "keep_firing_for": "0s", + "grafana_alert": { + "title": "RecordingRule1", + "data": [ + { + "refId": "A", + "queryType": "", + "relativeTimeRange": { + "from": 0, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "expression": "1 + 1", + "intervalMs": 1000, + "maxDataPoints": 43200, + "type": "math" + } + } + ], + "updated": "2023-09-29T17:37:19Z", + "intervalSeconds": 60, + "version": 1, + "uid": "", + "namespace_uid": "", + "rule_group": "Group1", + "is_paused": false, + "record": { + "metric": "test_metric", + "from": "A", + "target_datasource_uid": "test-datasource-uid" + }, + "metadata": {} + } } ] } diff --git a/pkg/tests/api/alerting/test-data/rulegroup-1-post.json b/pkg/tests/api/alerting/test-data/rulegroup-1-post.json index f9f1441eb18..e020299fc42 100644 --- a/pkg/tests/api/alerting/test-data/rulegroup-1-post.json +++ b/pkg/tests/api/alerting/test-data/rulegroup-1-post.json @@ -53,6 +53,26 @@ "exec_err_state": "Alerting", "missing_series_evals_to_resolve": 2 } + }, + { + "grafana_alert": { + "title": "RecordingRule1", + "data": [ + { + "refId": "A", + "datasourceUid": "__expr__", + "model": { + "expression": "1 + 1", + "type": "math" + } + } + ], + "record": { + "metric": "test_metric", + "from": "A", + "target_datasource_uid": "test-datasource-uid" + } + } } ] } diff --git a/pkg/tests/api/alerting/testing.go b/pkg/tests/api/alerting/testing.go index b49e61d3a4e..3bae37f209a 100644 --- a/pkg/tests/api/alerting/testing.go +++ b/pkg/tests/api/alerting/testing.go @@ -249,6 +249,7 @@ func convertGettableGrafanaRuleToPostable(gettable *apimodels.GettableGrafanaRul ExecErrState: gettable.ExecErrState, IsPaused: &gettable.IsPaused, NotificationSettings: gettable.NotificationSettings, + Record: gettable.Record, Metadata: gettable.Metadata, } } From 521cc11994369c21feb265203bfeda3d3f9d5583 Mon Sep 17 00:00:00 2001 From: Kristina Demeshchik Date: Tue, 23 Dec 2025 05:41:49 -0500 Subject: [PATCH 280/297] Dashboard Outline: Differentiate hover styles between edit and view modes (#115646) --- .../dashboard-scene/edit-pane/DashboardOutline.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx b/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx index 10bb180a4b1..b543b04537c 100644 --- a/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx +++ b/public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx @@ -94,7 +94,11 @@ function DashboardOutlineNode({ sceneObject, editPane, isEditing, depth, index } // eslint-disable-next-line @typescript-eslint/consistent-type-assertions style={{ '--depth': depth } as React.CSSProperties} > -
+
{isContainer && ( + + )} {loading && } {/* TODO: Better empty state https://github.com/grafana/grafana/issues/114804 */} {!loading && recentDashboards.length === 0 && ( diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 807d6fbfa4b..85563c43905 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -3748,6 +3748,8 @@ "recently-viewed": { "clear": "Clear history", "empty": "Nothing viewed yet", + "error": "Recently viewed dashboards couldn’t be loaded.", + "retry": "Retry", "title": "Recently viewed" }, "restore": { From 47436a3eebb64f45d2be85181585f503a68613f3 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Tue, 23 Dec 2025 16:16:48 +0200 Subject: [PATCH 283/297] Provisioning: Fix settings error loop (#115677) --- .../NestedFolderPicker/FolderRepo.tsx | 35 +++++++++++-------- .../components/BrowseView.tsx | 2 +- .../hooks/useGetResourceRepositoryView.ts | 7 ++-- .../hooks/useIsProvisionedInstance.ts | 19 +++++++--- 4 files changed, 40 insertions(+), 23 deletions(-) diff --git a/public/app/core/components/NestedFolderPicker/FolderRepo.tsx b/public/app/core/components/NestedFolderPicker/FolderRepo.tsx index 153becab1b9..9de19c4fcfe 100644 --- a/public/app/core/components/NestedFolderPicker/FolderRepo.tsx +++ b/public/app/core/components/NestedFolderPicker/FolderRepo.tsx @@ -14,13 +14,12 @@ export interface Props { } export const FolderRepo = memo(function FolderRepo({ folder }: Props) { - // skip rendering if: - // folder is not present - // folder have parentUID - // folder is not managed - // if whole instance is provisioned - const isProvisionedInstance = useIsProvisionedInstance(); - const skipRender = getShouldSkipRender(folder, isProvisionedInstance); + // Check if we can skip early without needing the useIsProvisionedInstance query + // This reduces RTK Query subscriptions and prevents re-render loops on API errors + const canSkipEarly = getCanSkipEarly(folder); + + const isProvisionedInstance = useIsProvisionedInstance({ skip: canSkipEarly }); + const skipRender = canSkipEarly || isProvisionedInstance; const { isReadOnlyRepo, repoType } = useGetResourceRepositoryView({ folderName: skipRender ? undefined : folder?.uid, @@ -51,11 +50,19 @@ export const FolderRepo = memo(function FolderRepo({ folder }: Props) { ); }); -function getShouldSkipRender(folder: FolderDTO | DashboardViewItem | undefined, isProvisionedInstance?: boolean) { - // Skip render if parentUID is present, then we should skip rendering. we only display icon for root folders - const hasParent = folder && Boolean('parentUID' in folder && folder.parentUID); - // Skip render if folder is not managed by Repo - const isNotManaged = folder && folder.managedBy !== ManagerKind.Repo; - - return !folder || hasParent || isNotManaged || isProvisionedInstance; +// Check conditions that don't require the useIsProvisionedInstance hook +function getCanSkipEarly(folder: FolderDTO | DashboardViewItem | undefined): boolean { + if (!folder) { + return true; + } + // Skip render if parentUID is present - we only display icon for root folders + const hasParent = Boolean('parentUID' in folder && folder.parentUID); + if (hasParent) { + return true; + } + const isNotManaged = folder.managedBy !== ManagerKind.Repo; + if (isNotManaged) { + return true; + } + return false; } diff --git a/public/app/features/browse-dashboards/components/BrowseView.tsx b/public/app/features/browse-dashboards/components/BrowseView.tsx index b28204f4fe2..0b42b79f9ae 100644 --- a/public/app/features/browse-dashboards/components/BrowseView.tsx +++ b/public/app/features/browse-dashboards/components/BrowseView.tsx @@ -43,10 +43,10 @@ export function BrowseView({ folderUID, width, height, permissions, isReadOnlyRe const selectedItems = useCheckboxSelectionState(); const childrenByParentUID = useChildrenByParentUIDState(); const canSelect = canSelectItems(permissions); - const isProvisionedInstance = useIsProvisionedInstance(); const provisioningEnabled = config.featureToggles.provisioning; const hasNoRole = contextSrv.user.orgRole === OrgRole.None; const { data: settingsData } = useGetFrontendSettingsQuery(!provisioningEnabled || hasNoRole ? skipToken : undefined); + const isProvisionedInstance = useIsProvisionedInstance({ settings: settingsData }); const rootItems = useSelector(rootItemsSelector); const [, stateManager] = useSearchStateManager(); diff --git a/public/app/features/provisioning/hooks/useGetResourceRepositoryView.ts b/public/app/features/provisioning/hooks/useGetResourceRepositoryView.ts index d612be620dc..9dc8e2b51c1 100644 --- a/public/app/features/provisioning/hooks/useGetResourceRepositoryView.ts +++ b/public/app/features/provisioning/hooks/useGetResourceRepositoryView.ts @@ -34,9 +34,10 @@ export const useGetResourceRepositoryView = ({ const hasNoRole = contextSrv.user.orgRole === OrgRole.None; const provisioningEnabled = config.featureToggles.provisioning; - const { data: settingsData, isLoading: isSettingsLoading } = useGetFrontendSettingsQuery( - !provisioningEnabled || skipQuery || hasNoRole ? skipToken : undefined - ); + const shouldSkipSettings = !provisioningEnabled || skipQuery || hasNoRole || (!name && !folderName); + const settingsQueryArg = shouldSkipSettings ? skipToken : undefined; + + const { data: settingsData, isLoading: isSettingsLoading } = useGetFrontendSettingsQuery(settingsQueryArg); const skipFolderQuery = !folderName || !provisioningEnabled || skipQuery || hasNoRole; const { data: folder, isLoading: isFolderLoading } = useGetFolderQuery( diff --git a/public/app/features/provisioning/hooks/useIsProvisionedInstance.ts b/public/app/features/provisioning/hooks/useIsProvisionedInstance.ts index 1c4ba12b8e4..61fcbb73da6 100644 --- a/public/app/features/provisioning/hooks/useIsProvisionedInstance.ts +++ b/public/app/features/provisioning/hooks/useIsProvisionedInstance.ts @@ -5,13 +5,22 @@ import { config } from '@grafana/runtime'; import { RepositoryViewList, useGetFrontendSettingsQuery } from 'app/api/clients/provisioning/v0alpha1'; import { contextSrv } from 'app/core/services/context_srv'; -export function useIsProvisionedInstance(settings?: RepositoryViewList) { +interface UseIsProvisionedInstanceOptions { + settings?: RepositoryViewList; + skip?: boolean; +} + +export function useIsProvisionedInstance(options: UseIsProvisionedInstanceOptions = {}) { + const { settings, skip: skipQuery } = options; const hasNoRole = contextSrv.user.orgRole === OrgRole.None; - const skip = !config.featureToggles.provisioning || hasNoRole; + const skip = !config.featureToggles.provisioning || hasNoRole || skipQuery; const settingsQuery = useGetFrontendSettingsQuery(settings || skip ? skipToken : undefined); - if (!settings) { - settings = settingsQuery.data; + + if (settingsQuery.isError) { + return false; } - return settings?.items?.some((item) => item.target === 'instance'); + + const effectiveSettings = settings ?? settingsQuery.data; + return effectiveSettings?.items?.some((item) => item.target === 'instance'); } From 45f665d203530292c038406a953913f92064be92 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Tue, 23 Dec 2025 15:24:53 +0100 Subject: [PATCH 284/297] Alerting: Config option to set default datasource in Prometheus rule import (#115665) What is this feature? Add a config option to set data source to imported rules when X-Grafana-Alerting-Datasource-UID is not present. Why do we need this feature? Currently mimirtool requires passing --extra-headers 'X-Grafana-Alerting-Datasource-UID: {uid}' when used with Grafana. This config option allows to specify a default, which is used when the header is missing, making it easier to use and more similar to the case when it's used with Mimir. --- conf/defaults.ini | 5 ++ conf/sample.ini | 5 ++ .../alerting-rules/alerting-migration.md | 2 + .../setup-grafana/configure-grafana/_index.md | 4 ++ .../ngalert/api/api_convert_prometheus.go | 3 + .../api/api_convert_prometheus_test.go | 60 +++++++++++++++++++ pkg/setting/setting_unified_alerting.go | 5 +- 7 files changed, 83 insertions(+), 1 deletion(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index 28cd0420eb2..363ca39d0c4 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1653,6 +1653,11 @@ loki_basic_auth_password = # Accepts duration formats like: 30s, 1m, 1h. rule_query_offset = 1m +# Default data source UID to use for query execution when importing Prometheus rules. +# This default is used when the X-Grafana-Alerting-Datasource-UID header is not provided. +# If not set, the header becomes required. +default_datasource_uid = + [recording_rules] # Enable recording rules. enabled = true diff --git a/conf/sample.ini b/conf/sample.ini index 0bb5b82fdc9..530b14c87ac 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -1615,6 +1615,11 @@ max_annotations_to_keep = # Accepts duration formats like: 30s, 1m, 1h. rule_query_offset = 1m +# Default data source UID to use for query execution when importing Prometheus rules. +# This default is used when the X-Grafana-Alerting-Datasource-UID header is not provided. +# If not set, the header becomes required. +default_datasource_uid = + #################################### Recording Rules ##################### [recording_rules] # Enable recording rules. diff --git a/docs/sources/alerting/alerting-rules/alerting-migration.md b/docs/sources/alerting/alerting-rules/alerting-migration.md index 3afa3aec453..ab2a5e995cd 100644 --- a/docs/sources/alerting/alerting-rules/alerting-migration.md +++ b/docs/sources/alerting/alerting-rules/alerting-migration.md @@ -242,6 +242,8 @@ Set to `true` to import recording rules in paused state. The UID of the data source to use for alert rule queries. +If not specified in the header, Grafana uses the configured default from `unified_alerting.prometheus_conversion.default_datasource_uid`. If neither the header nor the configuration option is provided, the request fails. + #### `X-Grafana-Alerting-Target-Datasource-UID` The UID of the target data source for recording rules. If not specified, the value from `X-Grafana-Alerting-Datasource-UID` is used. diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index 05d9cb66228..67c361b2bdc 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -2052,6 +2052,10 @@ This section applies only to rules imported as Grafana-managed rules. For more i Set the query offset to imported Grafana-managed rules when `query_offset` is not defined in the original rule group configuration. The default value is `1m`. +#### `default_datasource_uid` + +Set the default data source UID to use for query execution when importing Prometheus rules. Grafana uses this default when the `X-Grafana-Alerting-Datasource-UID` header isn't provided during import. If this option isn't set, the header becomes required. The default value is empty. +
### `[annotations]` diff --git a/pkg/services/ngalert/api/api_convert_prometheus.go b/pkg/services/ngalert/api/api_convert_prometheus.go index 79198ababf1..b6849f55dfc 100644 --- a/pkg/services/ngalert/api/api_convert_prometheus.go +++ b/pkg/services/ngalert/api/api_convert_prometheus.go @@ -375,6 +375,9 @@ func (srv *ConvertPrometheusSrv) RouteConvertPrometheusPostRuleGroups(c *context } datasourceUID := strings.TrimSpace(c.Req.Header.Get(datasourceUIDHeader)) + if datasourceUID == "" { + datasourceUID = srv.cfg.PrometheusConversion.DefaultDatasourceUID + } if datasourceUID == "" { return response.Err(errDatasourceUIDHeaderMissing) } diff --git a/pkg/services/ngalert/api/api_convert_prometheus_test.go b/pkg/services/ngalert/api/api_convert_prometheus_test.go index b4377431249..74e851ed195 100644 --- a/pkg/services/ngalert/api/api_convert_prometheus_test.go +++ b/pkg/services/ngalert/api/api_convert_prometheus_test.go @@ -75,6 +75,46 @@ func TestRouteConvertPrometheusPostRuleGroup(t *testing.T) { require.Contains(t, string(response.Body()), "Missing datasource UID header") }) + t.Run("without datasource UID header but with config default should succeed", func(t *testing.T) { + srv, _, ruleStore := createConvertPrometheusSrv(t) + // Set the config default + srv.cfg.PrometheusConversion.DefaultDatasourceUID = existingDSUID + + rc := createRequestCtx() + rc.Req.Header.Set(datasourceUIDHeader, "") + + response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup) + + require.Equal(t, http.StatusAccepted, response.Status()) + + // Verify that the config default datasource was used + assertRulesUseDatasource(t, ruleStore, existingDSUID, 2) + }) + + t.Run("header should take precedence over config default", func(t *testing.T) { + srv, dsCache, ruleStore := createConvertPrometheusSrv(t) + // Add another datasource + anotherDS := &datasources.DataSource{ + UID: "another-ds", + Type: datasources.DS_PROMETHEUS, + } + dsCache.DataSources = append(dsCache.DataSources, anotherDS) + + // Set the config default to one DS + srv.cfg.PrometheusConversion.DefaultDatasourceUID = "another-ds" + + // But use the header to specify a different one + rc := createRequestCtx() + rc.Req.Header.Set(datasourceUIDHeader, existingDSUID) + + response := srv.RouteConvertPrometheusPostRuleGroup(rc, "test", simpleGroup) + + require.Equal(t, http.StatusAccepted, response.Status()) + + // Verify that the header datasource was used, not the config default + assertRulesUseDatasource(t, ruleStore, existingDSUID, 2) + }) + t.Run("with invalid datasource should return error", func(t *testing.T) { srv, _, _ := createConvertPrometheusSrv(t) rc := createRequestCtx() @@ -1761,6 +1801,26 @@ func createRequestCtx() *contextmodel.ReqContext { } } +// assertRulesUseDatasource retrieves all alert rules from the store and verifies they use the expected datasource +func assertRulesUseDatasource(t *testing.T, ruleStore *fakes.RuleStore, expectedDatasourceUID string, expectedRuleCount int) { + t.Helper() + + rules, err := ruleStore.ListAlertRules(context.Background(), &models.ListAlertRulesQuery{ + OrgID: 1, + }) + require.NoError(t, err) + require.Len(t, rules, expectedRuleCount) + + for _, rule := range rules { + if rule.Record == nil { + require.NotEmpty(t, rule.Data) + require.Equal(t, expectedDatasourceUID, rule.Data[0].DatasourceUID, rule.Title, expectedDatasourceUID) + } else { + require.Equal(t, expectedDatasourceUID, rule.Record.TargetDatasourceUID) + } + } +} + // Test parseBooleanHeader function which handles boolean header values func TestParseBooleanHeader(t *testing.T) { headerName := "X-Test-Header" diff --git a/pkg/setting/setting_unified_alerting.go b/pkg/setting/setting_unified_alerting.go index 0733e8241e6..743f386ff52 100644 --- a/pkg/setting/setting_unified_alerting.go +++ b/pkg/setting/setting_unified_alerting.go @@ -190,6 +190,8 @@ type UnifiedAlertingReservedLabelSettings struct { type UnifiedAlertingPrometheusConversionSettings struct { // RuleQueryOffset defines a time offset to apply to rule queries during conversion from Prometheus to Grafana format RuleQueryOffset time.Duration + // DefaultDatasourceUID is the default datasource UID to use when converting Prometheus rules if not specified via header + DefaultDatasourceUID string } type UnifiedAlertingLokiSettings struct { @@ -536,7 +538,8 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error { prometheusConversion := iniFile.Section("unified_alerting.prometheus_conversion") uaCfg.PrometheusConversion = UnifiedAlertingPrometheusConversionSettings{ - RuleQueryOffset: prometheusConversion.Key("rule_query_offset").MustDuration(time.Minute), + RuleQueryOffset: prometheusConversion.Key("rule_query_offset").MustDuration(time.Minute), + DefaultDatasourceUID: prometheusConversion.Key("default_datasource_uid").MustString(""), } rr := iniFile.Section("recording_rules") From 0a0f92e85ea6d319c8e7501e435672608e3e3884 Mon Sep 17 00:00:00 2001 From: Sergej-Vlasov <37613182+Sergej-Vlasov@users.noreply.github.com> Date: Tue, 23 Dec 2025 15:52:50 +0000 Subject: [PATCH 285/297] InspectJsonTab: Force render the layout after change to reflect new gridPos (#115688) force render the layout after inspect panel change to account for gridPos change --- .../inspect/InspectJsonTab.test.tsx | 47 ++++++++++++++++++- .../inspect/InspectJsonTab.tsx | 7 +++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx b/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx index 36337a6ddef..786ff98e236 100644 --- a/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx +++ b/public/app/features/dashboard-scene/inspect/InspectJsonTab.test.tsx @@ -12,7 +12,7 @@ import { } from '@grafana/data'; import { getPanelPlugin } from '@grafana/data/test'; import { setPluginImportUtils, setRunRequest } from '@grafana/runtime'; -import { SceneCanvasText, SceneDataTransformer, SceneQueryRunner, VizPanel } from '@grafana/scenes'; +import { SceneCanvasText, SceneDataTransformer, SceneGridLayout, SceneQueryRunner, VizPanel } from '@grafana/scenes'; import * as libpanels from 'app/features/library-panels/state/api'; import { getStandardTransformers } from 'app/features/transformers/standardTransformers'; @@ -183,6 +183,51 @@ describe('InspectJsonTab', () => { expect(tab.state.onClose).toHaveBeenCalled(); }); + it('Can update gridPos and forces layout re-render', async () => { + const { tab, panel, scene } = await buildTestScene(); + + // Get the layout manager and spy on the grid's forceRender + const layoutManager = scene.state.body as DefaultGridLayoutManager; + const grid = layoutManager.state.grid as SceneGridLayout; + const forceRenderSpy = jest.spyOn(grid, 'forceRender'); + + const originalGridItem = panel.parent as DashboardGridItem; + expect(originalGridItem.state.x).toBe(0); + expect(originalGridItem.state.y).toBe(0); + expect(originalGridItem.state.width).toBe(8); + expect(originalGridItem.state.height).toBe(10); + + tab.onCodeEditorBlur(`{ + "id": 12, + "type": "table", + "title": "Panel A", + "gridPos": { + "x": 5, + "y": 10, + "w": 12, + "h": 8 + }, + "options": {}, + "fieldConfig": {}, + "transformations": [], + "transparent": false + }`); + + tab.onApplyChange(); + + const panel2 = findVizPanelByKey(scene, panel.state.key)!; + const gridItem = panel2.parent as DashboardGridItem; + + // Verify all gridPos properties are updated + expect(gridItem.state.x).toBe(5); + expect(gridItem.state.y).toBe(10); + expect(gridItem.state.width).toBe(12); + expect(gridItem.state.height).toBe(8); + + // Verify forceRender was called on the layout to apply position changes + expect(forceRenderSpy).toHaveBeenCalled(); + }); + it('Can show panel json for V2 dashboard specification', async () => { const { tab } = await buildTestSceneWithV2Spec(); diff --git a/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx b/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx index 4fd9bc8b9a6..60f3073dea1 100644 --- a/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx +++ b/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx @@ -9,6 +9,7 @@ import { SceneDataTransformer, sceneGraph, SceneGridItemStateLike, + SceneGridLayout, SceneObjectBase, SceneObjectRef, SceneObjectState, @@ -168,6 +169,12 @@ export class InspectJsonTab extends SceneObjectBase { panel.parent.setState(newState); + // Force the grid layout to re-render with the new positions + const layout = sceneGraph.getLayout(panel); + if (layout instanceof SceneGridLayout) { + layout.forceRender(); + } + //Report relevant updates reportPanelInspectInteraction(InspectTab.JSON, 'apply', { panel_type_changed: panel.state.pluginId !== panelModel.type, From a1389bc17319a4d1069d75ebc315eaeee3703ff2 Mon Sep 17 00:00:00 2001 From: "alerting-team[bot]" <158350966+alerting-team[bot]@users.noreply.github.com> Date: Tue, 23 Dec 2025 14:46:44 -0500 Subject: [PATCH 286/297] Alerting: Update alerting module to 77a1e2f35be87bebc41a0bf634f336282f0b9b53 (#115498) * [create-pull-request] automated change * Remove IsProtectedField and temp structure * Fix alerting historian * make update-workspace --------- Co-authored-by: yuri-tceretian <25988953+yuri-tceretian@users.noreply.github.com> Co-authored-by: Yuri Tseretyan Co-authored-by: Alexander Akhmetov --- apps/advisor/go.mod | 2 +- apps/advisor/go.sum | 4 +- apps/alerting/historian/go.mod | 2 +- apps/alerting/historian/go.sum | 4 +- .../pkg/app/notification/lokireader.go | 31 ++--- .../pkg/app/notification/lokireader_test.go | 106 ++++++++++-------- apps/iam/go.mod | 2 +- apps/iam/go.sum | 4 +- apps/plugins/go.mod | 2 +- apps/plugins/go.sum | 4 +- go.mod | 2 +- go.sum | 4 +- go.work.sum | 22 ++-- pkg/api/alerting.go | 64 ++--------- pkg/services/ngalert/models/receivers_diff.go | 55 +-------- .../alert-notifiers-v2-snapshot.json | 20 ++++ 16 files changed, 131 insertions(+), 197 deletions(-) diff --git a/apps/advisor/go.mod b/apps/advisor/go.mod index c1b2c7acd25..efc9ed4d500 100644 --- a/apps/advisor/go.mod +++ b/apps/advisor/go.mod @@ -157,7 +157,7 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/google/wire v0.7.0 // indirect - github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 // indirect + github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 // indirect github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect diff --git a/apps/advisor/go.sum b/apps/advisor/go.sum index 760641a8857..07730457d60 100644 --- a/apps/advisor/go.sum +++ b/apps/advisor/go.sum @@ -619,8 +619,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 h1:ZzG/gCclEit9w0QUfQt9GURcOycAIGcsQAhY1u0AEX0= -github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= +github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 h1:m5VqerdocNDh1vTi8itJmX9DwonGqj+SfkO0JxPHt0E= +github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg= github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY= diff --git a/apps/alerting/historian/go.mod b/apps/alerting/historian/go.mod index 0524e1a3852..9a83b79c0f6 100644 --- a/apps/alerting/historian/go.mod +++ b/apps/alerting/historian/go.mod @@ -4,7 +4,7 @@ go 1.25.5 require ( github.com/go-kit/log v0.2.1 - github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 + github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 github.com/grafana/grafana-app-sdk v0.48.7 github.com/grafana/grafana-app-sdk/logging v0.48.7 diff --git a/apps/alerting/historian/go.sum b/apps/alerting/historian/go.sum index 6e82a1dea7b..17beef468f0 100644 --- a/apps/alerting/historian/go.sum +++ b/apps/alerting/historian/go.sum @@ -243,8 +243,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 h1:ZzG/gCclEit9w0QUfQt9GURcOycAIGcsQAhY1u0AEX0= -github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= +github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 h1:m5VqerdocNDh1vTi8itJmX9DwonGqj+SfkO0JxPHt0E= +github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= diff --git a/apps/alerting/historian/pkg/app/notification/lokireader.go b/apps/alerting/historian/pkg/app/notification/lokireader.go index c26519e59b4..636ae083d91 100644 --- a/apps/alerting/historian/pkg/app/notification/lokireader.go +++ b/apps/alerting/historian/pkg/app/notification/lokireader.go @@ -31,6 +31,10 @@ const ( maxLimit = 1000 Namespace = "grafana" Subsystem = "alerting" + + // LogQL field path for alert rule UID after JSON parsing. + // Loki flattens nested JSON fields with underscores: alert.labels.__alert_rule_uid__ -> alert_labels___alert_rule_uid__ + lokiAlertRuleUIDField = "alert_labels___alert_rule_uid__" ) var ( @@ -111,13 +115,13 @@ func buildQuery(query Query) (string, error) { fmt.Sprintf(`%s=%q`, historian.LabelFrom, historian.LabelFromValue), } - if query.RuleUID != nil { - selectors = append(selectors, - fmt.Sprintf(`%s=%q`, historian.LabelRuleUID, *query.RuleUID)) - } - logql := fmt.Sprintf(`{%s} | json`, strings.Join(selectors, `,`)) + // Add ruleUID filter as JSON line filter if specified. + if query.RuleUID != nil && *query.RuleUID != "" { + logql += fmt.Sprintf(` | %s = %q`, lokiAlertRuleUIDField, *query.RuleUID) + } + // Add receiver filter if specified. if query.Receiver != nil && *query.Receiver != "" { logql += fmt.Sprintf(` | receiver = %q`, *query.Receiver) @@ -211,16 +215,13 @@ func parseLokiEntry(s lokiclient.Sample) (Entry, error) { groupLabels = make(map[string]string) } - alerts := make([]EntryAlert, len(lokiEntry.Alerts)) - for i, a := range lokiEntry.Alerts { - alerts[i] = EntryAlert{ - Status: a.Status, - Labels: a.Labels, - Annotations: a.Annotations, - StartsAt: a.StartsAt, - EndsAt: a.EndsAt, - } - } + alerts := []EntryAlert{{ + Status: lokiEntry.Alert.Status, + Labels: lokiEntry.Alert.Labels, + Annotations: lokiEntry.Alert.Annotations, + StartsAt: lokiEntry.Alert.StartsAt, + EndsAt: lokiEntry.Alert.EndsAt, + }} return Entry{ Timestamp: s.T, diff --git a/apps/alerting/historian/pkg/app/notification/lokireader_test.go b/apps/alerting/historian/pkg/app/notification/lokireader_test.go index 708c9d10df1..c9c35cb1e62 100644 --- a/apps/alerting/historian/pkg/app/notification/lokireader_test.go +++ b/apps/alerting/historian/pkg/app/notification/lokireader_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/grafana/alerting/models" "github.com/grafana/alerting/notify/historian" "github.com/grafana/alerting/notify/historian/lokiclient" "github.com/grafana/grafana-app-sdk/logging" @@ -133,9 +134,8 @@ func TestBuildQuery(t *testing.T) { query: Query{ RuleUID: stringPtr("test-rule-uid"), }, - expected: fmt.Sprintf(`{%s=%q,%s=%q} | json`, - historian.LabelFrom, historian.LabelFromValue, - historian.LabelRuleUID, "test-rule-uid"), + expected: fmt.Sprintf(`{%s=%q} | json | alert_labels___alert_rule_uid__ = "test-rule-uid"`, + historian.LabelFrom, historian.LabelFromValue), }, { name: "query with receiver filter", @@ -143,9 +143,8 @@ func TestBuildQuery(t *testing.T) { RuleUID: stringPtr("test-rule-uid"), Receiver: stringPtr("email-receiver"), }, - expected: fmt.Sprintf(`{%s=%q,%s=%q} | json | receiver = "email-receiver"`, - historian.LabelFrom, historian.LabelFromValue, - historian.LabelRuleUID, "test-rule-uid"), + expected: fmt.Sprintf(`{%s=%q} | json | alert_labels___alert_rule_uid__ = "test-rule-uid" | receiver = "email-receiver"`, + historian.LabelFrom, historian.LabelFromValue), }, { name: "query with status filter", @@ -153,9 +152,8 @@ func TestBuildQuery(t *testing.T) { RuleUID: stringPtr("test-rule-uid"), Status: createStatusPtr(v0alpha1.CreateNotificationqueryRequestNotificationStatusFiring), }, - expected: fmt.Sprintf(`{%s=%q,%s=%q} | json | status = "firing"`, - historian.LabelFrom, historian.LabelFromValue, - historian.LabelRuleUID, "test-rule-uid"), + expected: fmt.Sprintf(`{%s=%q} | json | alert_labels___alert_rule_uid__ = "test-rule-uid" | status = "firing"`, + historian.LabelFrom, historian.LabelFromValue), }, { name: "query with success outcome filter", @@ -163,9 +161,8 @@ func TestBuildQuery(t *testing.T) { RuleUID: stringPtr("test-rule-uid"), Outcome: outcomePtr(v0alpha1.CreateNotificationqueryRequestNotificationOutcomeSuccess), }, - expected: fmt.Sprintf(`{%s=%q,%s=%q} | json | error = ""`, - historian.LabelFrom, historian.LabelFromValue, - historian.LabelRuleUID, "test-rule-uid"), + expected: fmt.Sprintf(`{%s=%q} | json | alert_labels___alert_rule_uid__ = "test-rule-uid" | error = ""`, + historian.LabelFrom, historian.LabelFromValue), }, { name: "query with error outcome filter", @@ -173,9 +170,8 @@ func TestBuildQuery(t *testing.T) { RuleUID: stringPtr("test-rule-uid"), Outcome: outcomePtr(v0alpha1.CreateNotificationqueryRequestNotificationOutcomeError), }, - expected: fmt.Sprintf(`{%s=%q,%s=%q} | json | error != ""`, - historian.LabelFrom, historian.LabelFromValue, - historian.LabelRuleUID, "test-rule-uid"), + expected: fmt.Sprintf(`{%s=%q} | json | alert_labels___alert_rule_uid__ = "test-rule-uid" | error != ""`, + historian.LabelFrom, historian.LabelFromValue), }, { name: "query with many filters", @@ -185,9 +181,8 @@ func TestBuildQuery(t *testing.T) { Status: createStatusPtr(v0alpha1.CreateNotificationqueryRequestNotificationStatusResolved), Outcome: outcomePtr(v0alpha1.CreateNotificationqueryRequestNotificationOutcomeSuccess), }, - expected: fmt.Sprintf(`{%s=%q,%s=%q} | json | receiver = "email-receiver" | status = "resolved" | error = ""`, - historian.LabelFrom, historian.LabelFromValue, - historian.LabelRuleUID, "test-rule-uid"), + expected: fmt.Sprintf(`{%s=%q} | json | alert_labels___alert_rule_uid__ = "test-rule-uid" | receiver = "email-receiver" | status = "resolved" | error = ""`, + historian.LabelFrom, historian.LabelFromValue), }, { name: "query with group label matcher", @@ -277,19 +272,19 @@ func TestParseLokiEntry(t *testing.T) { GroupLabels: map[string]string{ "alertname": "test-alert", }, - Alerts: []historian.NotificationHistoryLokiEntryAlert{ - { - Status: "firing", - Labels: map[string]string{ - "severity": "critical", - }, - Annotations: map[string]string{ - "summary": "Test alert", - }, - StartsAt: now, - EndsAt: now.Add(1 * time.Hour), + Alert: historian.NotificationHistoryLokiEntryAlert{ + Status: "firing", + Labels: map[string]string{ + "severity": "critical", }, + Annotations: map[string]string{ + "summary": "Test alert", + }, + StartsAt: now, + EndsAt: now.Add(1 * time.Hour), }, + AlertIndex: 0, + AlertCount: 1, Retry: false, Duration: 100, PipelineTime: now, @@ -335,7 +330,9 @@ func TestParseLokiEntry(t *testing.T) { Error: "notification failed", GroupKey: "key:thing", GroupLabels: map[string]string{}, - Alerts: []historian.NotificationHistoryLokiEntryAlert{}, + Alert: historian.NotificationHistoryLokiEntryAlert{}, + AlertIndex: 0, + AlertCount: 1, PipelineTime: now, }), }, @@ -347,7 +344,7 @@ func TestParseLokiEntry(t *testing.T) { Outcome: OutcomeError, GroupKey: "key:thing", GroupLabels: map[string]string{}, - Alerts: []EntryAlert{}, + Alerts: []EntryAlert{{}}, Error: stringPtr("notification failed"), PipelineTime: now, }, @@ -365,7 +362,7 @@ func TestParseLokiEntry(t *testing.T) { Status: Status("firing"), Outcome: OutcomeSuccess, GroupLabels: map[string]string{}, - Alerts: []EntryAlert{}, + Alerts: []EntryAlert{{}}, PipelineTime: now, }, }, @@ -448,7 +445,9 @@ func TestLokiReader_RunQuery(t *testing.T) { Receiver: "receiver-1", Status: "firing", GroupLabels: map[string]string{}, - Alerts: []historian.NotificationHistoryLokiEntryAlert{}, + Alert: historian.NotificationHistoryLokiEntryAlert{}, + AlertIndex: 0, + AlertCount: 1, PipelineTime: now, }), }, @@ -459,7 +458,9 @@ func TestLokiReader_RunQuery(t *testing.T) { Receiver: "receiver-3", Status: "firing", GroupLabels: map[string]string{}, - Alerts: []historian.NotificationHistoryLokiEntryAlert{}, + Alert: historian.NotificationHistoryLokiEntryAlert{}, + AlertIndex: 0, + AlertCount: 1, PipelineTime: now, }), }, @@ -474,7 +475,9 @@ func TestLokiReader_RunQuery(t *testing.T) { Receiver: "receiver-2", Status: "firing", GroupLabels: map[string]string{}, - Alerts: []historian.NotificationHistoryLokiEntryAlert{}, + Alert: historian.NotificationHistoryLokiEntryAlert{}, + AlertIndex: 0, + AlertCount: 1, PipelineTime: now, }), }, @@ -546,19 +549,19 @@ func createMockLokiResponse(timestamp time.Time) lokiclient.QueryRes { GroupLabels: map[string]string{ "alertname": "test-alert", }, - Alerts: []historian.NotificationHistoryLokiEntryAlert{ - { - Status: "firing", - Labels: map[string]string{ - "severity": "critical", - }, - Annotations: map[string]string{ - "summary": "Test alert", - }, - StartsAt: timestamp, - EndsAt: timestamp.Add(1 * time.Hour), + Alert: historian.NotificationHistoryLokiEntryAlert{ + Status: "firing", + Labels: map[string]string{ + "severity": "critical", }, + Annotations: map[string]string{ + "summary": "Test alert", + }, + StartsAt: timestamp, + EndsAt: timestamp.Add(1 * time.Hour), }, + AlertIndex: 0, + AlertCount: 1, Retry: false, Duration: 100, PipelineTime: timestamp, @@ -587,10 +590,19 @@ func createLokiEntryJSONWithNilLabels(t *testing.T, timestamp time.Time) string "status": "firing", "error": "", "groupLabels": null, - "alerts": [], + "alert": {}, + "alertIndex": 0, + "alertCount": 1, "retry": false, "duration": 0, "pipelineTime": "%s" }`, timestamp.Format(time.RFC3339Nano)) return jsonStr } + +func TestRuleUIDLabelConstant(t *testing.T) { + // Verify that models.RuleUIDLabel has the expected value. + // If this changes in the alerting module, our LogQL field path constant will be incorrect + // and filtering for a single alert rule by its UID will break. + assert.Equal(t, "__alert_rule_uid__", models.RuleUIDLabel) +} diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 24769ca825f..8a6cec152cd 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -223,7 +223,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 // indirect + github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 // indirect github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect diff --git a/apps/iam/go.sum b/apps/iam/go.sum index 4584bbd9cc1..00d85d14de4 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -827,8 +827,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 h1:ZzG/gCclEit9w0QUfQt9GURcOycAIGcsQAhY1u0AEX0= -github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= +github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 h1:m5VqerdocNDh1vTi8itJmX9DwonGqj+SfkO0JxPHt0E= +github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg= github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY= diff --git a/apps/plugins/go.mod b/apps/plugins/go.mod index edcf18ea3e3..62f8f4edf0f 100644 --- a/apps/plugins/go.mod +++ b/apps/plugins/go.mod @@ -90,7 +90,7 @@ require ( github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 // indirect + github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 // indirect github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect diff --git a/apps/plugins/go.sum b/apps/plugins/go.sum index c5fbc7a39a5..f2dbfce834a 100644 --- a/apps/plugins/go.sum +++ b/apps/plugins/go.sum @@ -213,8 +213,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 h1:ZzG/gCclEit9w0QUfQt9GURcOycAIGcsQAhY1u0AEX0= -github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= +github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 h1:m5VqerdocNDh1vTi8itJmX9DwonGqj+SfkO0JxPHt0E= +github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg= github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY= diff --git a/go.mod b/go.mod index fb1ab1ce189..492087be19f 100644 --- a/go.mod +++ b/go.mod @@ -87,7 +87,7 @@ require ( github.com/googleapis/gax-go/v2 v2.15.0 // @grafana/grafana-backend-group github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // @grafana/grafana-app-platform-squad - github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 // @grafana/alerting-backend + github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 // @grafana/alerting-backend github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics diff --git a/go.sum b/go.sum index c1a8d8ad808..9d7d7380b71 100644 --- a/go.sum +++ b/go.sum @@ -1622,8 +1622,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7 h1:ZzG/gCclEit9w0QUfQt9GURcOycAIGcsQAhY1u0AEX0= -github.com/grafana/alerting v0.0.0-20251212143239-491433b332b7/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= +github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 h1:m5VqerdocNDh1vTi8itJmX9DwonGqj+SfkO0JxPHt0E= +github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg= github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY= diff --git a/go.work.sum b/go.work.sum index 73813d12650..ca22b546c86 100644 --- a/go.work.sum +++ b/go.work.sum @@ -793,7 +793,15 @@ github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5 github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs= +github.com/go-openapi/swag/fileutils v0.25.1/go.mod h1:+NXtt5xNZZqmpIpjqcujqojGFek9/w55b3ecmOdtg8M= +github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo= +github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc= +github.com/go-openapi/swag/mangling v0.25.1/go.mod h1:CdiMQ6pnfAgyQGSOIYnZkXvqhnnwOn997uXZMAd/7mQ= +github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg= +github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8= +github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= @@ -982,7 +990,6 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9K github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= @@ -1404,7 +1411,6 @@ github.com/richardartoul/molecule v1.0.0/go.mod h1:uvX/8buq8uVeiZiFht+0lqSLBHF+u github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= @@ -1623,7 +1629,6 @@ go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5queth go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/collector v0.121.0/go.mod h1:M4TlnmkjIgishm2DNCk9K3hMKTmAsY9w8cNFsp9EchM= go.opentelemetry.io/collector v0.124.0/go.mod h1:QzERYfmHUedawjr8Ph/CBEEkVqWS8IlxRLAZt+KHlCg= go.opentelemetry.io/collector/client v1.29.0/go.mod h1:LCUoEV2KCTKA1i+/txZaGsSPVWUcqeOV6wCfNsAippE= @@ -1839,6 +1844,7 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= go.opentelemetry.io/contrib/otelconf v0.15.0 h1:BLNiIUsrNcqhSKpsa6CnhE6LdrpY1A8X0szMVsu99eo= go.opentelemetry.io/contrib/otelconf v0.15.0/go.mod h1:OPH1seO5z9dp1P26gnLtoM9ht7JDvh3Ws6XRHuXqImY= go.opentelemetry.io/contrib/propagators/aws v1.37.0 h1:cp8AFiM/qjBm10C/ATIRnEDXpD5MBknrA0ANw4T2/ss= @@ -1910,7 +1916,6 @@ go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v8 go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= -go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= @@ -2118,8 +2123,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go. google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= -google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw= google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= +google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822 h1:zWFRixYR5QlotL+Uv3YfsPRENIrQFXiGs+iwqel6fOQ= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= @@ -2150,10 +2155,9 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -2177,7 +2181,6 @@ google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7E google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= -google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20 h1:MLBCGN1O7GzIx+cBiwfYPwtmZ41U3Mn/cotLJciaArI= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0= @@ -2299,7 +2302,6 @@ sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ih sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk= sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/structured-merge-diff/v6 v6.2.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 8fb2f366bf2..27abb6ebbfc 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -10,7 +10,6 @@ import ( "github.com/grafana/grafana/pkg/api/response" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" - "github.com/grafana/grafana/pkg/services/ngalert/models" ) func (hs *HTTPServer) GetAlertNotifiers() func(*contextmodel.ReqContext) response.Response { @@ -24,13 +23,13 @@ func (hs *HTTPServer) GetAlertNotifiers() func(*contextmodel.ReqContext) respons } type NotifierPlugin struct { - Type string `json:"type"` - TypeAlias string `json:"typeAlias,omitempty"` - Name string `json:"name"` - Heading string `json:"heading"` - Description string `json:"description"` - Info string `json:"info"` - Options []Field `json:"options"` + Type string `json:"type"` + TypeAlias string `json:"typeAlias,omitempty"` + Name string `json:"name"` + Heading string `json:"heading"` + Description string `json:"description"` + Info string `json:"info"` + Options []schema.Field `json:"options"` } result := make([]*NotifierPlugin, 0, len(v2)) @@ -45,56 +44,9 @@ func (hs *HTTPServer) GetAlertNotifiers() func(*contextmodel.ReqContext) respons Description: s.Description, Heading: s.Heading, Info: s.Info, - Options: schemaFieldsToFields(s.Type, nil, v1.Options), + Options: v1.Options, }) } return response.JSON(http.StatusOK, result) } } - -type Field struct { - Element schema.ElementType `json:"element"` - InputType schema.InputType `json:"inputType"` - Label string `json:"label"` - Description string `json:"description"` - Placeholder string `json:"placeholder"` - PropertyName string `json:"propertyName"` - SelectOptions []schema.SelectOption `json:"selectOptions"` - ShowWhen schema.ShowWhen `json:"showWhen"` - Required bool `json:"required"` - Protected bool `json:"protected,omitempty"` - ValidationRule string `json:"validationRule"` - Secure bool `json:"secure"` - DependsOn string `json:"dependsOn"` - SubformOptions []Field `json:"subformOptions"` -} - -func schemaFieldsToFields(iType schema.IntegrationType, parent schema.IntegrationFieldPath, fields []schema.Field) []Field { - if fields == nil { - return nil - } - result := make([]Field, 0, len(fields)) - for _, f := range fields { - result = append(result, schemaFieldToField(iType, parent, f)) - } - return result -} - -func schemaFieldToField(iType schema.IntegrationType, parent schema.IntegrationFieldPath, f schema.Field) Field { - return Field{ - Element: f.Element, - InputType: f.InputType, - Label: f.Label, - Description: f.Description, - Placeholder: f.Placeholder, - PropertyName: f.PropertyName, - SelectOptions: f.SelectOptions, - ShowWhen: f.ShowWhen, - Required: f.Required, - ValidationRule: f.ValidationRule, - Secure: f.Secure, - DependsOn: f.DependsOn, - SubformOptions: schemaFieldsToFields(iType, append(parent, f.PropertyName), f.SubformOptions), - Protected: models.IsProtectedField(iType, append(parent, f.PropertyName)), - } -} diff --git a/pkg/services/ngalert/models/receivers_diff.go b/pkg/services/ngalert/models/receivers_diff.go index bfe9328542f..681f07601d9 100644 --- a/pkg/services/ngalert/models/receivers_diff.go +++ b/pkg/services/ngalert/models/receivers_diff.go @@ -169,62 +169,9 @@ func HasIntegrationsDifferentProtectedFields(existing, incoming *Integration) [] var result []schema.IntegrationFieldPath settingsDiff := diff.GetSettingsPaths() for _, path := range settingsDiff { - if IsProtectedField(incoming.Config.Type(), path) { + if incoming.Config.IsProtectedField(path) { result = append(result, path) } } return result } - -// IsProtectedField returns true if the field at the given path is existing protected one. -// This includes: -// 1. URL fields marked as secure in the schema (e.g., webhook URLs with credentials) -// 2. URL fields NOT marked as secure but could contain credentials (e.g., API endpoints) -func IsProtectedField(integrationType schema.IntegrationType, path schema.IntegrationFieldPath) bool { - str := strings.ToLower(string(integrationType)) - pathStr := path.String() - - switch str { - case "prometheus-alertmanager": - return pathStr == "url" - case "dingding": - return pathStr == "url" // marked as secure - case "discord": - return pathStr == "url" // marked as secure (webhook URL) - case "googlechat": - return pathStr == "url" // marked as secure - case "jira": - return pathStr == "api_url" - case "kafka": - return pathStr == "kafkaRestProxy" - case "line": - return false - case "mqtt": - return pathStr == "brokerUrl" - case "oncall": - return pathStr == "url" - case "opsgenie": - return pathStr == "apiUrl" - case "pagerduty": - return pathStr == "url" - case "sensugo": - return pathStr == "url" - case "slack": - return pathStr == "url" || pathStr == "endpointUrl" - case "teams": - return pathStr == "url" - case "victorops": - return pathStr == "url" // marked as secure - case "webex": - return pathStr == "api_url" - case "webhook": - return pathStr == "url" || - pathStr == "http_config.oauth2.token_url" || - pathStr == "http_config.oauth2.proxy_config.proxy_url" - case "wecom": - return pathStr == "url" || // marked as secure - pathStr == "endpointUrl" - default: - return false - } -} diff --git a/pkg/tests/api/alerting/test-data/alert-notifiers-v2-snapshot.json b/pkg/tests/api/alerting/test-data/alert-notifiers-v2-snapshot.json index d942144ef9c..50c92e4d069 100644 --- a/pkg/tests/api/alerting/test-data/alert-notifiers-v2-snapshot.json +++ b/pkg/tests/api/alerting/test-data/alert-notifiers-v2-snapshot.json @@ -93,6 +93,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": true, "dependsOn": "", @@ -225,6 +226,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": true, "dependsOn": "", @@ -1300,6 +1302,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": true, "dependsOn": "", @@ -1405,6 +1408,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -2476,6 +2480,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -2645,6 +2650,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -2935,6 +2941,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -3139,6 +3146,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -4405,6 +4413,7 @@ "is": "" }, "required": false, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -5334,6 +5343,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -6630,6 +6640,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -6928,6 +6939,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": true, "dependsOn": "token", @@ -6946,6 +6958,7 @@ "is": "" }, "required": false, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -9237,6 +9250,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -11515,6 +11529,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": true, "dependsOn": "", @@ -12308,6 +12323,7 @@ "is": "" }, "required": false, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -13001,6 +13017,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -13443,6 +13460,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -13641,6 +13659,7 @@ "is": "" }, "required": false, + "protected": true, "validationRule": "", "secure": false, "dependsOn": "", @@ -15072,6 +15091,7 @@ "is": "" }, "required": true, + "protected": true, "validationRule": "", "secure": true, "dependsOn": "secret", From f5218b5eb826f1ea8561c24abf679e67a6a9bd88 Mon Sep 17 00:00:00 2001 From: Paul Marbach Date: Tue, 23 Dec 2025 16:39:30 -0500 Subject: [PATCH 287/297] Sparkline: Add point annotations for some common calcs (#115595) --- .../src/field/fieldDisplay.test.ts | 77 +++++++++- .../grafana-data/src/field/fieldDisplay.ts | 137 +++++++++++------- .../RadialGauge/RadialSparkline.tsx | 2 +- .../src/components/Sparkline/Sparkline.tsx | 10 +- .../src/components/Sparkline/utils.test.ts | 135 ++++++++++++++++- .../src/components/Sparkline/utils.ts | 56 +++++-- 6 files changed, 338 insertions(+), 79 deletions(-) diff --git a/packages/grafana-data/src/field/fieldDisplay.test.ts b/packages/grafana-data/src/field/fieldDisplay.test.ts index 718c0e54430..5ec3ed7ba4f 100644 --- a/packages/grafana-data/src/field/fieldDisplay.test.ts +++ b/packages/grafana-data/src/field/fieldDisplay.test.ts @@ -3,11 +3,18 @@ import { merge } from 'lodash'; import { toDataFrame } from '../dataframe/processDataFrame'; import { createTheme } from '../themes/createTheme'; import { ReducerID } from '../transformations/fieldReducer'; +import { FieldType } from '../types/dataFrame'; import { FieldConfigPropertyItem } from '../types/fieldOverrides'; import { MappingType, SpecialValueMatch, ValueMapping } from '../types/valueMapping'; import { getDisplayProcessor } from './displayProcessor'; -import { fixCellTemplateExpressions, getFieldDisplayValues, GetFieldDisplayValuesOptions } from './fieldDisplay'; +import { + FieldSparkline, + fixCellTemplateExpressions, + getFieldDisplayValues, + GetFieldDisplayValuesOptions, + getSparklineHighlight, +} from './fieldDisplay'; import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry'; describe('FieldDisplay', () => { @@ -556,3 +563,71 @@ describe('fixCellTemplateExpressions', () => { ); }); }); + +describe('getSparklineHighlight', () => { + const sparkline: FieldSparkline = { + y: { name: 'A', type: FieldType.number, values: [null, 2, 3, 4, 10, 8, 8, 8, 9, null], config: {} }, + }; + + it.each([ + { + calc: ReducerID.last, + expected: { + type: 'point', + xIdx: 9, + }, + }, + { + calc: ReducerID.max, + expected: { + type: 'point', + xIdx: 4, + }, + }, + { + calc: ReducerID.min, + expected: { + type: 'point', + xIdx: 1, + }, + }, + { + calc: ReducerID.first, + expected: { + type: 'point', + xIdx: 0, + }, + }, + { + calc: ReducerID.firstNotNull, + expected: { + type: 'point', + xIdx: 1, + }, + }, + { + calc: ReducerID.lastNotNull, + expected: { + type: 'point', + xIdx: 8, + }, + }, + { + calc: ReducerID.mean, + expected: { + type: 'line', + y: 6.5, + }, + }, + { + calc: ReducerID.median, + expected: { + type: 'line', + y: 8, + }, + }, + ])('it calculates the correct highlight for the $calc', ({ calc, expected }) => { + const result = getSparklineHighlight(sparkline, calc); + expect(result).toEqual(expected); + }); +}); diff --git a/packages/grafana-data/src/field/fieldDisplay.ts b/packages/grafana-data/src/field/fieldDisplay.ts index 3d82f571926..3496f419395 100644 --- a/packages/grafana-data/src/field/fieldDisplay.ts +++ b/packages/grafana-data/src/field/fieldDisplay.ts @@ -3,7 +3,7 @@ import { isEmpty } from 'lodash'; import { DataFrameView } from '../dataframe/DataFrameView'; import { getTimeField } from '../dataframe/processDataFrame'; import { GrafanaTheme2 } from '../themes/types'; -import { reduceField, ReducerID } from '../transformations/fieldReducer'; +import { isReducerID, reduceField, ReducerID } from '../transformations/fieldReducer'; import { getFieldMatcher } from '../transformations/matchers'; import { FieldMatcherID } from '../transformations/matchers/ids'; import { ScopedVars } from '../types/ScopedVars'; @@ -43,6 +43,7 @@ export interface FieldSparkline { x?: Field; // if this does not exist, use the index timeRange?: TimeRange; // Optionally force an absolute time highlightIndex?: number; + highlightLine?: number; } export interface FieldDisplay { @@ -72,6 +73,76 @@ export interface GetFieldDisplayValuesOptions { export const DEFAULT_FIELD_DISPLAY_VALUES_LIMIT = 25; +interface SparklineHighlightPoint { + type: 'point'; + xIdx: number; +} + +interface SparklineHighlightLine { + type: 'line'; + y: number; +} + +export function getSparklineHighlight( + sparkline: FieldSparkline, + calc: ReducerID +): SparklineHighlightPoint | SparklineHighlightLine | void { + switch (calc) { + case ReducerID.last: + return { type: 'point', xIdx: sparkline.y.values.length - 1 }; + case ReducerID.first: + return { type: 'point', xIdx: 0 }; + case ReducerID.lastNotNull: { + for (let k = sparkline.y.values.length - 1; k >= 0; k--) { + const v = sparkline.y.values[k]; + if (v !== null && v !== undefined && !Number.isNaN(v)) { + return { type: 'point', xIdx: k }; + } + } + return; + } + case ReducerID.firstNotNull: { + for (let k = 0; k < sparkline.y.values.length; k++) { + const v = sparkline.y.values[k]; + if (v !== null && v !== undefined && !Number.isNaN(v)) { + return { type: 'point', xIdx: k }; + } + } + return; + } + case ReducerID.min: { + let minIdx = -1; + let prevMin = Infinity; + for (let k = 0; k < sparkline.y.values.length; k++) { + const v = sparkline.y.values[k]; + if (v !== null && v !== undefined && !Number.isNaN(v) && v < prevMin) { + prevMin = v; + minIdx = k; + } + } + return minIdx >= 0 ? { type: 'point', xIdx: minIdx } : undefined; + } + case ReducerID.max: { + let maxIdx = -1; + let prevMax = -Infinity; + for (let k = 0; k < sparkline.y.values.length; k++) { + const v = sparkline.y.values[k]; + if (v !== null && v !== undefined && !Number.isNaN(v) && v > prevMax) { + prevMax = v; + maxIdx = k; + } + } + return maxIdx >= 0 ? { type: 'point', xIdx: maxIdx } : undefined; + } + case ReducerID.mean: + return { type: 'line', y: reduceField({ field: sparkline.y, reducers: [ReducerID.mean] }).mean }; + case ReducerID.median: + return { type: 'line', y: reduceField({ field: sparkline.y, reducers: [ReducerID.median] }).median }; + default: + return; + } +} + export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): FieldDisplay[] => { const { replaceVariables, reduceOptions, timeZone, theme } = options; const calcs = reduceOptions.calcs.length ? reduceOptions.calcs : [ReducerID.last]; @@ -190,62 +261,16 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi y: dataFrame.fields[i], x: timeField, }; - let highlightIdx: number | undefined = (() => { - switch (calc) { - case ReducerID.last: - return sparkline.y.values.length - 1; - case ReducerID.first: - return 0; - // TODO: #112977 enable more reducers for highlight index - // case ReducerID.lastNotNull: { - // for (let k = sparkline.y.values.length - 1; k >= 0; k--) { - // const v = sparkline.y.values[k]; - // if (v !== null && v !== undefined && !Number.isNaN(v)) { - // return k; - // } - // } - // return; - // } - // case ReducerID.firstNotNull: { - // for (let k = 0; k < sparkline.y.values.length; k++) { - // const v = sparkline.y.values[k]; - // if (v !== null && v !== undefined && !Number.isNaN(v)) { - // return k; - // } - // } - // return; - // } - // case ReducerID.min: { - // let minIdx = -1; - // let prevMin = Infinity; - // for (let k = 0; k < sparkline.y.values.length; k++) { - // const v = sparkline.y.values[k]; - // if (v !== null && v !== undefined && !Number.isNaN(v) && v < prevMin) { - // prevMin = v; - // minIdx = k; - // } - // } - // return minIdx >= 0 ? minIdx : undefined; - // } - // case ReducerID.max: { - // let maxIdx = -1; - // let prevMax = -Infinity; - // for (let k = 0; k < sparkline.y.values.length; k++) { - // const v = sparkline.y.values[k]; - // if (v !== null && v !== undefined && !Number.isNaN(v) && v > prevMax) { - // prevMax = v; - // maxIdx = k; - // } - // } - // return maxIdx >= 0 ? maxIdx : undefined; - // } - default: - return; + if (isReducerID(calc)) { + const sparklineHighlight = getSparklineHighlight(sparkline, calc); + switch (sparklineHighlight?.type) { + case 'point': + sparkline.highlightIndex = sparklineHighlight.xIdx; + break; + case 'line': + sparkline.highlightLine = sparklineHighlight.y; + break; } - })(); - - if (typeof highlightIdx === 'number') { - sparkline.highlightIndex = highlightIdx; } } diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialSparkline.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialSparkline.tsx index 2d6c45a14bf..4a52d5241d5 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialSparkline.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialSparkline.tsx @@ -67,7 +67,7 @@ export const RadialSparkline = memo( return (
- +
); } diff --git a/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx b/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx index c18b235e757..d1fb4f3b0e0 100644 --- a/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx +++ b/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx @@ -14,18 +14,18 @@ export interface SparklineProps extends Themeable2 { height: number; config?: FieldConfig; sparkline: FieldSparkline; + showHighlights?: boolean; } -const SparklineFn: React.FC = memo((props) => { - const { sparkline, config: fieldConfig, theme, width, height } = props; - - const { frame: alignedDataFrame, warning } = prepareSeries(sparkline, fieldConfig); +export const SparklineFn: React.FC = memo((props) => { + const { sparkline, config: fieldConfig, theme, width, height, showHighlights } = props; + const { frame: alignedDataFrame, warning } = prepareSeries(sparkline, theme, fieldConfig, showHighlights); if (warning) { return null; } const data = preparePlotData2(alignedDataFrame, getStackingGroups(alignedDataFrame)); - const configBuilder = prepareConfig(sparkline, alignedDataFrame, theme); + const configBuilder = prepareConfig(sparkline, alignedDataFrame, theme, showHighlights); return ; }); diff --git a/packages/grafana-ui/src/components/Sparkline/utils.test.ts b/packages/grafana-ui/src/components/Sparkline/utils.test.ts index ca49f6da512..0ec65515e0c 100644 --- a/packages/grafana-ui/src/components/Sparkline/utils.test.ts +++ b/packages/grafana-ui/src/components/Sparkline/utils.test.ts @@ -1,6 +1,6 @@ -import { Field, FieldSparkline, FieldType } from '@grafana/data'; +import { createTheme, Field, FieldSparkline, FieldType, toDataFrame } from '@grafana/data'; -import { getYRange, preparePlotFrame } from './utils'; +import { getYRange, prepareConfig, preparePlotFrame } from './utils'; describe('Prepare Sparkline plot frame', () => { it('should return sorted array if x-axis numeric', () => { @@ -201,3 +201,134 @@ describe('Get y range', () => { expect(actual[0]).toBeLessThan(actual[1]!); }); }); + +describe('prepareConfig', () => { + it('should not throw an error if there are multiple values', () => { + const sparkline: FieldSparkline = { + x: { + name: 'x', + values: [1679839200000, 1680444000000, 1681048800000, 1681653600000, 1682258400000], + type: FieldType.time, + config: {}, + }, + y: { + name: 'y', + values: [1, 2, 3, 4, 5], + type: FieldType.number, + config: {}, + }, + }; + + const dataFrame = toDataFrame({ + fields: [sparkline.x, sparkline.y], + }); + + const config = prepareConfig(sparkline, dataFrame, createTheme()); + expect(config.series.length).toBe(1); + }); + + it('should not throw an error if there is a single value', () => { + const sparkline: FieldSparkline = { + x: { + name: 'x', + values: [1679839200000], + type: FieldType.time, + config: {}, + }, + y: { + name: 'y', + values: [1], + type: FieldType.number, + config: {}, + }, + }; + + const dataFrame = toDataFrame({ + fields: [sparkline.x, sparkline.y], + }); + + const config = prepareConfig(sparkline, dataFrame, createTheme()); + expect(config.series.length).toBe(1); + }); + + it('should not throw an error if there are no values', () => { + const sparkline: FieldSparkline = { + x: { + name: 'x', + values: [], + type: FieldType.time, + config: {}, + }, + y: { + name: 'y', + values: [], + type: FieldType.number, + config: {}, + }, + }; + + const dataFrame = toDataFrame({ + fields: [sparkline.x, sparkline.y], + }); + + const config = prepareConfig(sparkline, dataFrame, createTheme()); + expect(config.series.length).toBe(1); + }); + + it('should set up highlight series if showHighlights is true and highlightIdx exists', () => { + const sparkline: FieldSparkline = { + x: { + name: 'x', + values: [1679839200000, 1680444000000, 1681048800000, 1681653600000, 1682258400000], + type: FieldType.time, + config: {}, + }, + y: { + name: 'y', + values: [1, 2, 3, 4, 5], + type: FieldType.number, + config: {}, + }, + highlightIndex: 2, + }; + + const dataFrame = toDataFrame({ + fields: [sparkline.x, sparkline.y], + }); + + const config = prepareConfig(sparkline, dataFrame, createTheme(), true); + expect(config.series.length).toBe(1); + expect(config.series[0].getConfig().points).toEqual( + expect.objectContaining({ + show: true, + filter: [2], + }) + ); + }); + + it('should not set up highlight series if showHighlights is false even if highlightIdx exists', () => { + const sparkline: FieldSparkline = { + x: { + name: 'x', + values: [1679839200000, 1680444000000, 1681048800000, 1681653600000, 1682258400000], + type: FieldType.time, + config: {}, + }, + y: { + name: 'y', + values: [1, 2, 3, 4, 5], + type: FieldType.number, + config: {}, + }, + highlightIndex: 2, + }; + + const dataFrame = toDataFrame({ + fields: [sparkline.x, sparkline.y], + }); + + const config = prepareConfig(sparkline, dataFrame, createTheme(), false); + expect(config.series.length).toBe(1); + expect(config.series[0].getConfig().points?.show).not.toBe(true); + }); +}); diff --git a/packages/grafana-ui/src/components/Sparkline/utils.ts b/packages/grafana-ui/src/components/Sparkline/utils.ts index be24eb6c4e8..c1402c4da2d 100644 --- a/packages/grafana-ui/src/components/Sparkline/utils.ts +++ b/packages/grafana-ui/src/components/Sparkline/utils.ts @@ -2,6 +2,7 @@ import { Range } from 'uplot'; import { applyNullInsertThreshold, + // colorManipulator, DataFrame, FieldConfig, FieldSparkline, @@ -22,6 +23,7 @@ import { VisibilityMode, ScaleDirection, ScaleOrientation, + // FieldColorModeId, } from '@grafana/schema'; import { UPlotConfigBuilder } from '../uPlot/config/UPlotConfigBuilder'; @@ -112,8 +114,7 @@ export function getYRange(alignedFrame: DataFrame): Range.MinMax { return [roundedMin, roundedMax]; } -// TODO: #112977 enable highlight index -// const HIGHLIGHT_IDX_POINT_SIZE = 6; +const HIGHLIGHT_IDX_POINT_SIZE = 6; const defaultConfig: GraphFieldConfig = { drawStyle: GraphDrawStyle.Line, @@ -124,7 +125,9 @@ const defaultConfig: GraphFieldConfig = { export const prepareSeries = ( sparkline: FieldSparkline, - fieldConfig?: FieldConfig + _theme: GrafanaTheme2, + fieldConfig?: FieldConfig, + _showHighlights?: boolean ): { frame: DataFrame; warning?: string } => { const frame = nullToValue(preparePlotFrame(sparkline, fieldConfig)); if (frame.fields.some((f) => f.values.length <= 1)) { @@ -136,16 +139,41 @@ export const prepareSeries = ( frame, }; } + // TODO:rgb(24, 24, 24) will address this. + // if (showHighlights && typeof sparkline.highlightLine === 'number') { + // const highlightY = sparkline.highlightLine; + // const colorMode = getFieldColorModeForField(sparkline.y); + // const seriesColor = colorMode.getCalculator(sparkline.y, theme)(highlightY, 0); + // frame.fields.push({ + // name: 'highlightLine', + // type: FieldType.number, + // values: new Array(frame.length).fill(highlightY), + // config: { + // color: { + // mode: FieldColorModeId.Fixed, + // fixedColor: colorManipulator.lighten(seriesColor, 0.5), + // }, + // custom: { + // lineStyle: { + // fill: 'dash', + // dash: [5, 2], + // }, + // }, + // }, + // state: {}, + // }); + // } return { frame }; }; export const prepareConfig = ( sparkline: FieldSparkline, dataFrame: DataFrame, - theme: GrafanaTheme2 + theme: GrafanaTheme2, + showHighlights?: boolean ): UPlotConfigBuilder => { const builder = new UPlotConfigBuilder(); - // const rangePad = HIGHLIGHT_IDX_POINT_SIZE / 2; + const rangePad = HIGHLIGHT_IDX_POINT_SIZE / 2; builder.setCursor({ show: false, @@ -206,13 +234,14 @@ export const prepareConfig = ( const colorMode = getFieldColorModeForField(field); const seriesColor = colorMode.getCalculator(field, theme)(0, 0); - // TODO: #112977 enable highlight index and adjust padding accordingly - // const hasHighlightIndex = typeof sparkline.highlightIndex === 'number'; - // if (hasHighlightIndex) { - // builder.setPadding([rangePad, rangePad, rangePad, rangePad]); - // } + + const hasHighlightIndex = showHighlights && typeof sparkline.highlightIndex === 'number'; + if (hasHighlightIndex) { + builder.setPadding([rangePad, rangePad, rangePad, rangePad]); + } + const pointsMode = - customConfig.drawStyle === GraphDrawStyle.Points // || hasHighlightIndex + customConfig.drawStyle === GraphDrawStyle.Points || hasHighlightIndex ? VisibilityMode.Always : customConfig.showPoints; @@ -227,9 +256,8 @@ export const prepareConfig = ( lineWidth: customConfig.lineWidth, lineInterpolation: customConfig.lineInterpolation, showPoints: pointsMode, - // TODO: #112977 enable highlight index - pointSize: /* hasHighlightIndex ? HIGHLIGHT_IDX_POINT_SIZE : */ customConfig.pointSize, - // pointsFilter: hasHighlightIndex ? [sparkline.highlightIndex!] : undefined, + pointSize: hasHighlightIndex ? HIGHLIGHT_IDX_POINT_SIZE : customConfig.pointSize, + pointsFilter: hasHighlightIndex ? [sparkline.highlightIndex!] : undefined, fillOpacity: customConfig.fillOpacity, fillColor: customConfig.fillColor, lineStyle: customConfig.lineStyle, From 5e4e6c1172826351bd7c9aa689679d2b87bf61f2 Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Wed, 24 Dec 2025 00:42:01 +0000 Subject: [PATCH 288/297] I18n: Download translations from Crowdin (#115705) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 2 ++ public/locales/de-DE/grafana.json | 2 ++ public/locales/es-ES/grafana.json | 2 ++ public/locales/fr-FR/grafana.json | 2 ++ public/locales/hu-HU/grafana.json | 2 ++ public/locales/id-ID/grafana.json | 2 ++ public/locales/it-IT/grafana.json | 2 ++ public/locales/ja-JP/grafana.json | 2 ++ public/locales/ko-KR/grafana.json | 2 ++ public/locales/nl-NL/grafana.json | 2 ++ public/locales/pl-PL/grafana.json | 2 ++ public/locales/pt-BR/grafana.json | 2 ++ public/locales/pt-PT/grafana.json | 2 ++ public/locales/ru-RU/grafana.json | 2 ++ public/locales/sv-SE/grafana.json | 2 ++ public/locales/tr-TR/grafana.json | 2 ++ public/locales/zh-Hans/grafana.json | 2 ++ public/locales/zh-Hant/grafana.json | 2 ++ 18 files changed, 36 insertions(+) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 84cc597980b..2fb06738b36 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -3780,6 +3780,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index ef5f649123d..6c09564ae2f 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -3748,6 +3748,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 443dbefbc39..45d955ba66c 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -3748,6 +3748,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 1d98e007593..33bf748fbc0 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -3748,6 +3748,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index bfe9d3e9542..3704f01de9a 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -3748,6 +3748,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 86b18767abb..602acd8813a 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -3732,6 +3732,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 976d81b2f37..4832c6c744c 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -3748,6 +3748,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 9bfbc78a21e..c87617b2161 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -3732,6 +3732,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index bd4103a1de1..65d967807ea 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -3732,6 +3732,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index d5386284647..a1d9ba17c5b 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -3748,6 +3748,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index ad8f9b19b6a..c7d04e0cd8b 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -3780,6 +3780,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 8ad480fc30d..eee46fc8344 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -3748,6 +3748,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index 3fed004bfdf..415075e65ab 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -3748,6 +3748,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index 10cd0cdd7bb..a8aab23f3d0 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -3780,6 +3780,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index faca6a40afc..f3c4effc8b3 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -3748,6 +3748,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index ad957bd271f..7cd8b7b5939 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -3748,6 +3748,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index f02bcda5189..b36e525f676 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -3732,6 +3732,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index dc05987a2e6..0302a7ffb6f 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -3732,6 +3732,8 @@ "recently-viewed": { "clear": "", "empty": "", + "error": "", + "retry": "", "title": "" }, "restore": { From 3f5f0f783b5219243d04cad1de20e58bb4533b47 Mon Sep 17 00:00:00 2001 From: "alerting-team[bot]" <158350966+alerting-team[bot]@users.noreply.github.com> Date: Wed, 24 Dec 2025 08:48:06 +0100 Subject: [PATCH 289/297] Alerting: Update alerting module to 926c7491019668286c423cad9d2a65f419b14944 (#115704) [create-pull-request] automated change Co-authored-by: alexander-akhmetov <1875873+alexander-akhmetov@users.noreply.github.com> --- apps/advisor/go.mod | 2 +- apps/advisor/go.sum | 4 ++-- apps/alerting/historian/go.mod | 2 +- apps/alerting/historian/go.sum | 4 ++-- apps/iam/go.mod | 2 +- apps/iam/go.sum | 4 ++-- apps/plugins/go.mod | 2 +- apps/plugins/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/advisor/go.mod b/apps/advisor/go.mod index efc9ed4d500..646ceed9a86 100644 --- a/apps/advisor/go.mod +++ b/apps/advisor/go.mod @@ -157,7 +157,7 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/google/wire v0.7.0 // indirect - github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 // indirect + github.com/grafana/alerting v0.0.0-20251223160021-926c74910196 // indirect github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect diff --git a/apps/advisor/go.sum b/apps/advisor/go.sum index 07730457d60..750d9f97fc5 100644 --- a/apps/advisor/go.sum +++ b/apps/advisor/go.sum @@ -619,8 +619,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 h1:m5VqerdocNDh1vTi8itJmX9DwonGqj+SfkO0JxPHt0E= -github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= +github.com/grafana/alerting v0.0.0-20251223160021-926c74910196 h1:A9UJtyBBUE7PkRsAITKU05iz+HpHO9SaVjfdo2Df3UQ= +github.com/grafana/alerting v0.0.0-20251223160021-926c74910196/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg= github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY= diff --git a/apps/alerting/historian/go.mod b/apps/alerting/historian/go.mod index 9a83b79c0f6..fb624d65db3 100644 --- a/apps/alerting/historian/go.mod +++ b/apps/alerting/historian/go.mod @@ -4,7 +4,7 @@ go 1.25.5 require ( github.com/go-kit/log v0.2.1 - github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 + github.com/grafana/alerting v0.0.0-20251223160021-926c74910196 github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 github.com/grafana/grafana-app-sdk v0.48.7 github.com/grafana/grafana-app-sdk/logging v0.48.7 diff --git a/apps/alerting/historian/go.sum b/apps/alerting/historian/go.sum index 17beef468f0..0835100976a 100644 --- a/apps/alerting/historian/go.sum +++ b/apps/alerting/historian/go.sum @@ -243,8 +243,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 h1:m5VqerdocNDh1vTi8itJmX9DwonGqj+SfkO0JxPHt0E= -github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= +github.com/grafana/alerting v0.0.0-20251223160021-926c74910196 h1:A9UJtyBBUE7PkRsAITKU05iz+HpHO9SaVjfdo2Df3UQ= +github.com/grafana/alerting v0.0.0-20251223160021-926c74910196/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI= github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo= diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 8a6cec152cd..54689bc54f3 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -223,7 +223,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 // indirect + github.com/grafana/alerting v0.0.0-20251223160021-926c74910196 // indirect github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect diff --git a/apps/iam/go.sum b/apps/iam/go.sum index 00d85d14de4..28bf1486774 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -827,8 +827,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 h1:m5VqerdocNDh1vTi8itJmX9DwonGqj+SfkO0JxPHt0E= -github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= +github.com/grafana/alerting v0.0.0-20251223160021-926c74910196 h1:A9UJtyBBUE7PkRsAITKU05iz+HpHO9SaVjfdo2Df3UQ= +github.com/grafana/alerting v0.0.0-20251223160021-926c74910196/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg= github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY= diff --git a/apps/plugins/go.mod b/apps/plugins/go.mod index 62f8f4edf0f..a2657edda7a 100644 --- a/apps/plugins/go.mod +++ b/apps/plugins/go.mod @@ -90,7 +90,7 @@ require ( github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 // indirect + github.com/grafana/alerting v0.0.0-20251223160021-926c74910196 // indirect github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect diff --git a/apps/plugins/go.sum b/apps/plugins/go.sum index f2dbfce834a..f0c923083af 100644 --- a/apps/plugins/go.sum +++ b/apps/plugins/go.sum @@ -213,8 +213,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 h1:m5VqerdocNDh1vTi8itJmX9DwonGqj+SfkO0JxPHt0E= -github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= +github.com/grafana/alerting v0.0.0-20251223160021-926c74910196 h1:A9UJtyBBUE7PkRsAITKU05iz+HpHO9SaVjfdo2Df3UQ= +github.com/grafana/alerting v0.0.0-20251223160021-926c74910196/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg= github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY= diff --git a/go.mod b/go.mod index 492087be19f..f22d410c51f 100644 --- a/go.mod +++ b/go.mod @@ -87,7 +87,7 @@ require ( github.com/googleapis/gax-go/v2 v2.15.0 // @grafana/grafana-backend-group github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // @grafana/grafana-app-platform-squad - github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 // @grafana/alerting-backend + github.com/grafana/alerting v0.0.0-20251223160021-926c74910196 // @grafana/alerting-backend github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics diff --git a/go.sum b/go.sum index 9d7d7380b71..069d53dd5e9 100644 --- a/go.sum +++ b/go.sum @@ -1622,8 +1622,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8 h1:m5VqerdocNDh1vTi8itJmX9DwonGqj+SfkO0JxPHt0E= -github.com/grafana/alerting v0.0.0-20251217141753-77a1e2f35be8/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= +github.com/grafana/alerting v0.0.0-20251223160021-926c74910196 h1:A9UJtyBBUE7PkRsAITKU05iz+HpHO9SaVjfdo2Df3UQ= +github.com/grafana/alerting v0.0.0-20251223160021-926c74910196/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o= github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg= github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY= From 4f57ebe4ad636cbfc50b220bf0ce18ea6b4ac18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20Sencer=20=C3=96zcan?= <32759850+mustafasencer@users.noreply.github.com> Date: Wed, 24 Dec 2025 09:33:24 +0100 Subject: [PATCH 290/297] fix: bump default facet search limit for unified search (#115690) * fix: bump limit * feat: add facetLimit query parameter to search API * fix: set to 500 * fix: update snapshot * fix: yarn generate-apis --- .../rtkq/dashboard/v0alpha1/endpoints.gen.ts | 3 ++ pkg/registry/apis/dashboard/search.go | 20 +++++++++++- pkg/registry/apis/dashboard/search_test.go | 32 +++++++++++++++++++ .../dashboard.grafana.app-v0alpha1.json | 9 ++++++ public/app/features/search/service/unified.ts | 2 +- 5 files changed, 64 insertions(+), 2 deletions(-) diff --git a/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts index 5d3e72b13aa..b50a074e4a2 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts @@ -243,6 +243,7 @@ const injectedRtkApi = api type: queryArg['type'], folder: queryArg.folder, facet: queryArg.facet, + facetLimit: queryArg.facetLimit, tags: queryArg.tags, libraryPanel: queryArg.libraryPanel, permission: queryArg.permission, @@ -663,6 +664,8 @@ export type SearchDashboardsAndFoldersApiArg = { folder?: string; /** count distinct terms for selected fields */ facet?: string[]; + /** maximum number of terms to return per facet (default 50, max 1000) */ + facetLimit?: number; /** tag query filter */ tags?: string[]; /** find dashboards that reference a given libraryPanel */ diff --git a/pkg/registry/apis/dashboard/search.go b/pkg/registry/apis/dashboard/search.go index e28eeedcecc..08a943b8da6 100644 --- a/pkg/registry/apis/dashboard/search.go +++ b/pkg/registry/apis/dashboard/search.go @@ -115,6 +115,15 @@ func (s *SearchHandler) GetAPIRoutes(defs map[string]common.OpenAPIDefinition) * Schema: spec.ArrayProperty(spec.StringProperty()), }, }, + { + ParameterProps: spec3.ParameterProps{ + Name: "facetLimit", + In: "query", + Description: "maximum number of terms to return per facet (default 50, max 1000)", + Required: false, + Schema: spec.Int64Property(), + }, + }, { ParameterProps: spec3.ParameterProps{ Name: "tags", @@ -340,6 +349,7 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) { func convertHttpSearchRequestToResourceSearchRequest(queryParams url.Values, user identity.Requester, getDashboardsUIDsSharedWithUser func() ([]string, error)) (*resourcepb.ResourceSearchRequest, error) { // get limit and offset from query params limit := 50 + facetLimit := 50 offset := 0 page := 1 if queryParams.Has("limit") { @@ -422,11 +432,19 @@ func convertHttpSearchRequestToResourceSearchRequest(queryParams url.Values, use // The facet term fields if facets, ok := queryParams["facet"]; ok { + if queryParams.Has("facetLimit") { + if parsed, err := strconv.Atoi(queryParams.Get("facetLimit")); err == nil && parsed > 0 { + facetLimit = parsed + if facetLimit > 1000 { + facetLimit = 1000 + } + } + } searchRequest.Facet = make(map[string]*resourcepb.ResourceSearchRequest_Facet) for _, v := range facets { searchRequest.Facet[v] = &resourcepb.ResourceSearchRequest_Facet{ Field: v, - Limit: 50, + Limit: int64(facetLimit), } } } diff --git a/pkg/registry/apis/dashboard/search_test.go b/pkg/registry/apis/dashboard/search_test.go index 406494b9d36..3b9935f8247 100644 --- a/pkg/registry/apis/dashboard/search_test.go +++ b/pkg/registry/apis/dashboard/search_test.go @@ -818,6 +818,38 @@ func TestConvertHttpSearchRequestToResourceSearchRequest(t *testing.T) { Federated: []*resourcepb.ResourceKey{folderKey}, }, }, + "facet fields with custom limit": { + queryString: "facet=tags&facetLimit=500", + expected: &resourcepb.ResourceSearchRequest{ + Options: &resourcepb.ListOptions{Key: dashboardKey}, + Query: "", + Limit: 50, + Offset: 0, + Page: 1, + Explain: false, + Fields: defaultFields, + Facet: map[string]*resourcepb.ResourceSearchRequest_Facet{ + "tags": {Field: "tags", Limit: 500}, + }, + Federated: []*resourcepb.ResourceKey{folderKey}, + }, + }, + "facet fields with limit exceeding max": { + queryString: "facet=tags&facetLimit=5000", + expected: &resourcepb.ResourceSearchRequest{ + Options: &resourcepb.ListOptions{Key: dashboardKey}, + Query: "", + Limit: 50, + Offset: 0, + Page: 1, + Explain: false, + Fields: defaultFields, + Facet: map[string]*resourcepb.ResourceSearchRequest_Facet{ + "tags": {Field: "tags", Limit: 1000}, + }, + Federated: []*resourcepb.ResourceKey{folderKey}, + }, + }, "tag filter": { queryString: "tag=tag1&tag=tag2", expected: &resourcepb.ResourceSearchRequest{ diff --git a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v0alpha1.json index b65fa2ad0d7..61834093866 100644 --- a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v0alpha1.json @@ -1802,6 +1802,15 @@ } } }, + { + "name": "facetLimit", + "in": "query", + "description": "maximum number of terms to return per facet (default 50, max 1000)", + "schema": { + "type": "integer", + "format": "int64" + } + }, { "name": "tags", "in": "query", diff --git a/public/app/features/search/service/unified.ts b/public/app/features/search/service/unified.ts index 8d1af58f9d9..146a54d295d 100644 --- a/public/app/features/search/service/unified.ts +++ b/public/app/features/search/service/unified.ts @@ -106,7 +106,7 @@ export class UnifiedSearcher implements GrafanaSearcher { async tags(query: SearchQuery): Promise { const qry = query.query ?? '*'; - let uri = `${searchURI}?facet=tags&query=${qry}&limit=1`; + let uri = `${searchURI}?facet=tags&facetLimit=1000&query=${qry}&limit=1`; const resp = await getBackendSrv().get(uri); return resp.facets?.tags?.terms || []; } From c38e515dec1608c5072c6684a80e1f9ef96dc064 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Wed, 24 Dec 2025 09:49:38 +0100 Subject: [PATCH 291/297] Alerting: Fix export of imported Prometheus-style recording rules to terraform (#115661) Alerting: Fix export imported Prometheus-style recording rules to terraform --- .../ngalert/api/api_ruler_validation_test.go | 2 + pkg/services/ngalert/api/compat/compat.go | 84 +++++++++------- .../ngalert/api/compat/compat_test.go | 97 +++++++++++++++++++ .../api/validation/api_ruler_validation.go | 1 + pkg/services/ngalert/prom/convert.go | 10 +- pkg/services/ngalert/prom/convert_test.go | 7 +- 6 files changed, 158 insertions(+), 43 deletions(-) diff --git a/pkg/services/ngalert/api/api_ruler_validation_test.go b/pkg/services/ngalert/api/api_ruler_validation_test.go index 98fbb20cf12..553a02cff30 100644 --- a/pkg/services/ngalert/api/api_ruler_validation_test.go +++ b/pkg/services/ngalert/api/api_ruler_validation_test.go @@ -493,6 +493,7 @@ func TestValidateRuleNode_NoUID(t *testing.T) { r.GrafanaManagedAlert.NoDataState = apimodels.OK r.GrafanaManagedAlert.ExecErrState = apimodels.AlertingErrState r.GrafanaManagedAlert.NotificationSettings = &apimodels.AlertRuleNotificationSettings{} + r.GrafanaManagedAlert.MissingSeriesEvalsToResolve = util.Pointer[int64](1) r.For = func() *model.Duration { five := model.Duration(time.Second * 5); return &five }() r.KeepFiringFor = func() *model.Duration { five := model.Duration(time.Second * 5); return &five }() return &r @@ -502,6 +503,7 @@ func TestValidateRuleNode_NoUID(t *testing.T) { require.Empty(t, alert.NoDataState) require.Empty(t, alert.ExecErrState) require.Nil(t, alert.NotificationSettings) + require.Nil(t, alert.MissingSeriesEvalsToResolve) require.Zero(t, alert.For) require.Zero(t, alert.KeepFiringFor) }, diff --git a/pkg/services/ngalert/api/compat/compat.go b/pkg/services/ngalert/api/compat/compat.go index 5fda13672ba..37c9a8db6ed 100644 --- a/pkg/services/ngalert/api/compat/compat.go +++ b/pkg/services/ngalert/api/compat/compat.go @@ -189,42 +189,11 @@ func AlertRuleExportFromAlertRule(rule models.AlertRule) (definitions.AlertRuleE data = append(data, query) } - cPtr := &rule.Condition - if rule.Condition == "" { - cPtr = nil - } - - noDataState := definitions.NoDataState(rule.NoDataState) - ndsPtr := &noDataState - if noDataState == "" { - ndsPtr = nil - } - execErrorState := definitions.ExecutionErrorState(rule.ExecErrState) - eesPtr := &execErrorState - if execErrorState == "" { - eesPtr = nil - } - result := definitions.AlertRuleExport{ - UID: rule.UID, - Title: rule.Title, - For: model.Duration(rule.For), - KeepFiringFor: model.Duration(rule.KeepFiringFor), - Condition: cPtr, - Data: data, - DashboardUID: rule.DashboardUID, - PanelID: rule.PanelID, - NoDataState: ndsPtr, - ExecErrState: eesPtr, - IsPaused: rule.IsPaused, - NotificationSettings: AlertRuleNotificationSettingsExportFromNotificationSettings(rule.NotificationSettings), - Record: AlertRuleRecordExportFromRecord(rule.Record), - } - if rule.For.Seconds() > 0 { - result.ForString = util.Pointer(model.Duration(rule.For).String()) - } - if rule.KeepFiringFor.Seconds() > 0 { - result.KeepFiringForString = util.Pointer(model.Duration(rule.KeepFiringFor).String()) + UID: rule.UID, + Title: rule.Title, + Data: data, + IsPaused: rule.IsPaused, } if rule.Annotations != nil { result.Annotations = &rule.Annotations @@ -232,13 +201,54 @@ func AlertRuleExportFromAlertRule(rule models.AlertRule) (definitions.AlertRuleE if rule.Labels != nil { result.Labels = &rule.Labels } - if rule.MissingSeriesEvalsToResolve != nil && *rule.MissingSeriesEvalsToResolve != -1 { - result.MissingSeriesEvalsToResolve = rule.MissingSeriesEvalsToResolve + + if rule.Type() == models.RuleTypeRecording { + populateRecordingRuleExportFields(rule, &result) + } else { + populateAlertingRuleExportFields(rule, &result) } return result, nil } +func populateRecordingRuleExportFields(rule models.AlertRule, result *definitions.AlertRuleExport) { + result.Record = AlertRuleRecordExportFromRecord(rule.Record) +} + +func populateAlertingRuleExportFields(rule models.AlertRule, result *definitions.AlertRuleExport) { + result.DashboardUID = rule.DashboardUID + result.PanelID = rule.PanelID + result.NotificationSettings = AlertRuleNotificationSettingsExportFromNotificationSettings(rule.NotificationSettings) + + if rule.Condition != "" { + result.Condition = &rule.Condition + } + + if rule.NoDataState != "" { + noDataState := definitions.NoDataState(rule.NoDataState) + result.NoDataState = &noDataState + } + + if rule.ExecErrState != "" { + execErrorState := definitions.ExecutionErrorState(rule.ExecErrState) + result.ExecErrState = &execErrorState + } + + result.For = model.Duration(rule.For) + if rule.For > 0 { + result.ForString = util.Pointer(model.Duration(rule.For).String()) + } + + result.KeepFiringFor = model.Duration(rule.KeepFiringFor) + if rule.KeepFiringFor > 0 { + result.KeepFiringForString = util.Pointer(model.Duration(rule.KeepFiringFor).String()) + } + + if rule.MissingSeriesEvalsToResolve != nil && *rule.MissingSeriesEvalsToResolve != -1 { + result.MissingSeriesEvalsToResolve = rule.MissingSeriesEvalsToResolve + } +} + func encodeQueryModel(m map[string]any) (string, error) { var buf bytes.Buffer enc := json.NewEncoder(&buf) diff --git a/pkg/services/ngalert/api/compat/compat_test.go b/pkg/services/ngalert/api/compat/compat_test.go index 4a107335945..8b50c609559 100644 --- a/pkg/services/ngalert/api/compat/compat_test.go +++ b/pkg/services/ngalert/api/compat/compat_test.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/util" ) func TestToModel(t *testing.T) { @@ -115,6 +116,102 @@ func TestToModel(t *testing.T) { }) } +func TestAlertRuleExportFromAlertRule(t *testing.T) { + alertingRule := models.RuleGen.With( + models.RuleGen.WithNotEmptyLabels(2, "lbl-"), + models.RuleGen.WithAnnotations(map[string]string{"ann-key": "ann-value"}), + models.RuleGen.WithFor(2*time.Minute), + models.RuleGen.WithKeepFiringFor(5*time.Minute), + models.RuleGen.WithNotificationSettingsGen(models.NotificationSettingsGen()), + ).Generate() + recordingRule := models.RuleGen.With( + models.RuleGen.WithAllRecordingRules(), + models.RuleGen.WithNotEmptyLabels(2, "lbl-"), + models.RuleGen.WithAnnotations(map[string]string{"ann-key": "ann-value"}), + ).Generate() + + // Build expected exported recording rule + recordingRuleData, err := AlertQueryExportFromAlertQuery(recordingRule.Data[0]) + require.NoError(t, err) + expectedRecordingRuleExport := definitions.AlertRuleExport{ + UID: recordingRule.UID, + Title: recordingRule.Title, + Data: []definitions.AlertQueryExport{recordingRuleData}, + Annotations: &recordingRule.Annotations, + Labels: &recordingRule.Labels, + Record: &definitions.AlertRuleRecordExport{ + Metric: recordingRule.Record.Metric, + From: recordingRule.Record.From, + TargetDatasourceUID: util.Pointer(recordingRule.Record.TargetDatasourceUID), + }, + } + + // Build expected exported alerting rule + alertingRuleData, err := AlertQueryExportFromAlertQuery(alertingRule.Data[0]) + require.NoError(t, err) + noDataState := definitions.NoDataState(alertingRule.NoDataState) + execErrState := definitions.ExecutionErrorState(alertingRule.ExecErrState) + expectedAlertingRuleExport := definitions.AlertRuleExport{ + UID: alertingRule.UID, + Title: alertingRule.Title, + Condition: &alertingRule.Condition, + Data: []definitions.AlertQueryExport{alertingRuleData}, + DashboardUID: alertingRule.DashboardUID, + PanelID: alertingRule.PanelID, + NoDataState: &noDataState, + ExecErrState: &execErrState, + For: prommodel.Duration(alertingRule.For), + KeepFiringFor: prommodel.Duration(alertingRule.KeepFiringFor), + ForString: util.Pointer(prommodel.Duration(alertingRule.For).String()), + KeepFiringForString: util.Pointer(prommodel.Duration(alertingRule.KeepFiringFor).String()), + Annotations: &alertingRule.Annotations, + Labels: &alertingRule.Labels, + NotificationSettings: AlertRuleNotificationSettingsExportFromNotificationSettings(alertingRule.NotificationSettings), + MissingSeriesEvalsToResolve: alertingRule.MissingSeriesEvalsToResolve, + } + + testCases := []struct { + name string + rule models.AlertRule + expected definitions.AlertRuleExport + }{ + { + name: "export recording rule", + rule: recordingRule, + expected: expectedRecordingRuleExport, + }, + { + name: "export alerting rule", + rule: alertingRule, + expected: expectedAlertingRuleExport, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + exported, err := AlertRuleExportFromAlertRule(tc.rule) + require.NoError(t, err) + require.Equal(t, tc.expected, exported) + }) + } +} + +func TestAlertQueryExportFromAlertQuery(t *testing.T) { + query := models.RuleGen.GenerateQuery() + + exported, err := AlertQueryExportFromAlertQuery(query) + require.NoError(t, err) + + require.Equal(t, query.RefID, exported.RefID) + require.Equal(t, query.DatasourceUID, exported.DatasourceUID) + require.Equal(t, int64(time.Duration(query.RelativeTimeRange.From).Seconds()), exported.RelativeTimeRange.FromSeconds) + require.Equal(t, int64(time.Duration(query.RelativeTimeRange.To).Seconds()), exported.RelativeTimeRange.ToSeconds) + require.NotNil(t, exported.QueryType) + require.Equal(t, query.QueryType, *exported.QueryType) + require.NotNil(t, exported.Model) + require.NotEmpty(t, exported.ModelString) +} + func TestAlertRuleMetadataFromModelMetadata(t *testing.T) { t.Run("should convert model metadata to api metadata", func(t *testing.T) { modelMetadata := models.AlertRuleMetadata{ diff --git a/pkg/services/ngalert/api/validation/api_ruler_validation.go b/pkg/services/ngalert/api/validation/api_ruler_validation.go index c2baf8108ba..5a74c58f90e 100644 --- a/pkg/services/ngalert/api/validation/api_ruler_validation.go +++ b/pkg/services/ngalert/api/validation/api_ruler_validation.go @@ -193,6 +193,7 @@ func validateRecordingRuleFields(in *apimodels.PostableExtendedRuleNode, newRule newRule.For = 0 newRule.KeepFiringFor = 0 newRule.NotificationSettings = nil + newRule.MissingSeriesEvalsToResolve = nil return newRule, nil } diff --git a/pkg/services/ngalert/prom/convert.go b/pkg/services/ngalert/prom/convert.go index 13c95e70aa4..0e8ebf647d4 100644 --- a/pkg/services/ngalert/prom/convert.go +++ b/pkg/services/ngalert/prom/convert.go @@ -272,16 +272,16 @@ func (p *Converter) convertRule(orgID int64, namespaceUID string, promGroup Prom RuleGroup: promGroup.Name, IsPaused: isPaused, Record: record, + } + + if !isRecordingRule { + result.NotificationSettings = p.cfg.NotificationSettings // MissingSeriesEvalsToResolve is set to 1 to match the Prometheus behaviour. // Prometheus resolves alerts as soon as the series disappears. // By setting this value to 1 we ensure that the alert is resolved on the first evaluation // that doesn't have the series. - MissingSeriesEvalsToResolve: util.Pointer[int64](1), - } - - if !isRecordingRule { - result.NotificationSettings = p.cfg.NotificationSettings + result.MissingSeriesEvalsToResolve = util.Pointer[int64](1) } if p.cfg.KeepOriginalRuleDefinition != nil && *p.cfg.KeepOriginalRuleDefinition { diff --git a/pkg/services/ngalert/prom/convert_test.go b/pkg/services/ngalert/prom/convert_test.go index 503cd76dd64..d9542e24c5e 100644 --- a/pkg/services/ngalert/prom/convert_test.go +++ b/pkg/services/ngalert/prom/convert_test.go @@ -358,7 +358,12 @@ func TestPrometheusRulesToGrafana(t *testing.T) { require.Equal(t, models.Duration(evalOffset), grafanaRule.Data[0].RelativeTimeRange.To) require.Equal(t, models.Duration(10*time.Minute+evalOffset), grafanaRule.Data[0].RelativeTimeRange.From) - require.Equal(t, util.Pointer(int64(1)), grafanaRule.MissingSeriesEvalsToResolve) + + if promRule.Record != "" { + require.Nil(t, grafanaRule.MissingSeriesEvalsToResolve) + } else { + require.Equal(t, util.Pointer(int64(1)), grafanaRule.MissingSeriesEvalsToResolve) + } require.Equal(t, models.OkErrState, grafanaRule.ExecErrState) require.Equal(t, models.OK, grafanaRule.NoDataState) From e38f007d305fc73beb4ad7c66697cd71a42a6071 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Wed, 24 Dec 2025 13:41:46 +0100 Subject: [PATCH 292/297] Alerting: Fetch alert rule provenances for a page of rules only (#115643) * Alerting: Fetch alert rule provenances for a page of rules only * error when failed to fetch provenance --- .../ngalert/api/api_prometheus_test.go | 134 ++++++++++++++++++ .../ngalert/api/prometheus/api_prometheus.go | 48 +++++-- .../ngalert/notifier/alertmanager_config.go | 1 + pkg/services/ngalert/provisioning/persist.go | 1 + .../provisioning/provisioning_store_mock.go | 61 ++++++++ .../ngalert/store/provisioning_store.go | 24 ++++ .../ngalert/store/provisioning_store_test.go | 49 +++++++ .../ngalert/tests/fakes/provisioning.go | 30 +++- 8 files changed, 331 insertions(+), 17 deletions(-) diff --git a/pkg/services/ngalert/api/api_prometheus_test.go b/pkg/services/ngalert/api/api_prometheus_test.go index 75ec6c901fd..dc0f6d1f13d 100644 --- a/pkg/services/ngalert/api/api_prometheus_test.go +++ b/pkg/services/ngalert/api/api_prometheus_test.go @@ -2369,6 +2369,140 @@ func TestRouteGetRuleStatuses(t *testing.T) { } }) + t.Run("multi-page pagination loads provenance correctly", func(t *testing.T) { + fakeStore, fakeAIM, api, fakeProvisioning := setupAPIFull(t) + + // Create 3 groups with 1 rule each: groups 1 and 3 firing, group 2 normal + for i := 1; i <= 3; i++ { + rule := gen.With(gen.WithOrgID(orgID), func(r *ngmodels.AlertRule) { + r.NamespaceUID = "ns-1" + r.RuleGroup = fmt.Sprintf("group-%d", i) + r.UID = fmt.Sprintf("rule-%d", i) + }, withClassicConditionSingleQuery()).GenerateRef() + + alertState := eval.Normal + if i != 2 { + alertState = eval.Alerting + } + fakeAIM.GenerateAlertInstances(orgID, rule.UID, 1, func(s *state.State) *state.State { + s.State = alertState + s.Labels = data.Labels{"test": "label"} + return s + }) + fakeStore.PutRule(context.Background(), rule) + } + + // Set provenance for all rules + err := fakeProvisioning.SetProvenance(context.Background(), + &ngmodels.AlertRule{UID: "rule-1", OrgID: orgID}, orgID, ngmodels.ProvenanceAPI) + require.NoError(t, err) + err = fakeProvisioning.SetProvenance(context.Background(), + &ngmodels.AlertRule{UID: "rule-3", OrgID: orgID}, orgID, ngmodels.ProvenanceFile) + require.NoError(t, err) + + // Request firing groups with group_limit=2 - fetches multiple pages, skipping group 2 + req, err := http.NewRequest("GET", "/api/v1/rules?state=firing&group_limit=2", nil) + require.NoError(t, err) + c := &contextmodel.ReqContext{ + Context: &web.Context{Req: req}, + SignedInUser: &user.SignedInUser{ + OrgID: orgID, + Permissions: queryPermissions, + }, + } + + resp := api.RouteGetRuleStatuses(c) + require.Equal(t, http.StatusOK, resp.Status()) + + var res apimodels.RuleResponse + require.NoError(t, json.Unmarshal(resp.Body(), &res)) + + // Should return 2 firing groups + require.Len(t, res.Data.RuleGroups, 2) + require.Equal(t, "group-1", res.Data.RuleGroups[0].Name) + require.Equal(t, apimodels.Provenance(ngmodels.ProvenanceAPI), res.Data.RuleGroups[0].Rules[0].Provenance) + require.Equal(t, "group-3", res.Data.RuleGroups[1].Name) + require.Equal(t, apimodels.Provenance(ngmodels.ProvenanceFile), res.Data.RuleGroups[1].Rules[0].Provenance) + }) + + t.Run("provenance fetch error returns error response in paginated mode", func(t *testing.T) { + fakeStore, fakeAIM, api, fakeProvisioning := setupAPIFull(t) + + rule := gen.With(gen.WithOrgID(orgID), func(r *ngmodels.AlertRule) { + r.NamespaceUID = "ns-1" + r.RuleGroup = "group-1" + r.UID = "rule-1" + }, withClassicConditionSingleQuery()).GenerateRef() + + fakeAIM.GenerateAlertInstances(orgID, rule.UID, 1, func(s *state.State) *state.State { + s.State = eval.Alerting + s.Labels = data.Labels{"test": "label"} + return s + }) + fakeStore.PutRule(context.Background(), rule) + + fakeProvisioning.GetProvenancesByUIDsFunc = func(ctx context.Context, orgID int64, resourceType string, uids []string) (map[string]ngmodels.Provenance, error) { + return nil, errors.New("database connection failed") + } + + req, err := http.NewRequest("GET", "/api/v1/rules?group_limit=10", nil) + require.NoError(t, err) + c := &contextmodel.ReqContext{ + Context: &web.Context{Req: req}, + SignedInUser: &user.SignedInUser{ + OrgID: orgID, + Permissions: queryPermissions, + }, + } + + resp := api.RouteGetRuleStatuses(c) + require.Equal(t, http.StatusInternalServerError, resp.Status()) + + var res apimodels.RuleResponse + require.NoError(t, json.Unmarshal(resp.Body(), &res)) + require.Equal(t, "error", res.Status) + require.Contains(t, res.Error, "failed to load provenance") + }) + + t.Run("provenance fetch error returns error response in non-paginated mode", func(t *testing.T) { + fakeStore, fakeAIM, api, fakeProvisioning := setupAPIFull(t) + + rule := gen.With(gen.WithOrgID(orgID), func(r *ngmodels.AlertRule) { + r.NamespaceUID = "ns-1" + r.RuleGroup = "group-1" + r.UID = "rule-1" + }, withClassicConditionSingleQuery()).GenerateRef() + + fakeAIM.GenerateAlertInstances(orgID, rule.UID, 1, func(s *state.State) *state.State { + s.State = eval.Alerting + s.Labels = data.Labels{"test": "label"} + return s + }) + fakeStore.PutRule(context.Background(), rule) + + fakeProvisioning.GetProvenancesFunc = func(ctx context.Context, orgID int64, resourceType string) (map[string]ngmodels.Provenance, error) { + return nil, errors.New("database connection failed") + } + + req, err := http.NewRequest("GET", "/api/v1/rules", nil) + require.NoError(t, err) + c := &contextmodel.ReqContext{ + Context: &web.Context{Req: req}, + SignedInUser: &user.SignedInUser{ + OrgID: orgID, + Permissions: queryPermissions, + }, + } + + resp := api.RouteGetRuleStatuses(c) + require.Equal(t, http.StatusInternalServerError, resp.Status()) + + var res apimodels.RuleResponse + require.NoError(t, json.Unmarshal(resp.Body(), &res)) + require.Equal(t, "error", res.Status) + require.Contains(t, res.Error, "failed to load provenance") + }) + t.Run("state filter continues when first page has no matches", func(t *testing.T) { fakeStore, fakeAIM, api := setupAPI(t) diff --git a/pkg/services/ngalert/api/prometheus/api_prometheus.go b/pkg/services/ngalert/api/prometheus/api_prometheus.go index b4e14a66cfe..077761d0caf 100644 --- a/pkg/services/ngalert/api/prometheus/api_prometheus.go +++ b/pkg/services/ngalert/api/prometheus/api_prometheus.go @@ -54,6 +54,7 @@ type StatusReader interface { type ProvenanceStore interface { GetProvenances(ctx context.Context, org int64, resourceType string) (map[string]ngmodels.Provenance, error) + GetProvenancesByUIDs(ctx context.Context, org int64, resourceType string, uids []string) (map[string]ngmodels.Provenance, error) } type PrometheusSrv struct { @@ -328,14 +329,6 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon span.AddEvent("User permissions checked") span.SetAttributes(attribute.Int("allowedNamespaces", len(allowedNamespaces))) - provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.GetOrgID(), (&ngmodels.AlertRule{}).ResourceType()) - if err != nil { - ruleResponse.Status = "error" - ruleResponse.Error = fmt.Sprintf("failed to get provenances visible to the user: %s", err.Error()) - ruleResponse.ErrorType = apiv1.ErrServer - return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse) - } - ruleResponse = PrepareRuleGroupStatusesV2( srv.log, srv.store, @@ -347,7 +340,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon }, RuleStatusMutatorGenerator(srv.status), RuleAlertStateMutatorGenerator(srv.manager), - provenanceRecords, + srv.provenanceStore, ) return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse) @@ -454,6 +447,7 @@ func RuleAlertStateMutatorGenerator(manager state.AlertInstanceManager) RuleAler type paginationContext struct { opts RuleGroupStatusesOptions provenanceRecords map[string]ngmodels.Provenance + provenanceStore ProvenanceStore ruleStatusMutator RuleStatusMutator alertStateMutator RuleAlertStateMutator @@ -532,6 +526,37 @@ func (ctx *paginationContext) fetchAndFilterPage(log log.Logger, store ListAlert ) span.AddEvent("Alert rules retrieved from store") + // Load provenance for this page's rules + if ctx.provenanceStore != nil { + maxGroups := getInt64WithDefault(ctx.opts.Query, "group_limit", -1) + maxRules := getInt64WithDefault(ctx.opts.Query, "rule_limit", -1) + + if maxGroups > 0 || maxRules > 0 { + // Paginated, fetch and merge provenances for this page + uids := make([]string, 0, len(ruleList)) + for _, rule := range ruleList { + uids = append(uids, rule.UID) + } + pageProvenances, err := ctx.provenanceStore.GetProvenancesByUIDs(ctx.opts.Ctx, ctx.opts.OrgID, (&ngmodels.AlertRule{}).ResourceType(), uids) + if err != nil { + return pageResult{}, fmt.Errorf("failed to load provenance: %w", err) + } + if ctx.provenanceRecords == nil { + ctx.provenanceRecords = pageProvenances + } else { + maps.Copy(ctx.provenanceRecords, pageProvenances) + } + } else if ctx.provenanceRecords == nil { + // Not paginated, fetch all once + var err error + ctx.provenanceRecords, err = ctx.provenanceStore.GetProvenances(ctx.opts.Ctx, ctx.opts.OrgID, (&ngmodels.AlertRule{}).ResourceType()) + if err != nil { + return pageResult{}, fmt.Errorf("failed to load provenance: %w", err) + } + } + } + span.AddEvent("Provenances retrieved from store") + groupedRules := getGroupedRules(log, ruleList, ctx.ruleNamesSet, ctx.opts.AllowedNamespaces) result := pageResult{ @@ -643,7 +668,7 @@ func paginateRuleGroups(log log.Logger, store ListAlertRulesStoreV2, ctx *pagina return allGroups, rulesTotals, continueToken, nil } -func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opts RuleGroupStatusesOptions, ruleStatusMutator RuleStatusMutator, alertStateMutator RuleAlertStateMutator, provenanceRecords map[string]ngmodels.Provenance) apimodels.RuleResponse { +func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opts RuleGroupStatusesOptions, ruleStatusMutator RuleStatusMutator, alertStateMutator RuleAlertStateMutator, provenanceStore ProvenanceStore) apimodels.RuleResponse { ctx, span := tracer.Start(opts.Ctx, "api.prometheus.PrepareRuleGroupStatusesV2") defer span.End() opts.Ctx = ctx @@ -835,7 +860,8 @@ func PrepareRuleGroupStatusesV2(log log.Logger, store ListAlertRulesStoreV2, opt span.SetAttributes(attribute.Bool("compact", compact)) pagCtx := &paginationContext{ opts: opts, - provenanceRecords: provenanceRecords, + provenanceRecords: nil, + provenanceStore: provenanceStore, ruleStatusMutator: ruleStatusMutator, alertStateMutator: alertStateMutator, namespaceUIDs: namespaceUIDs, diff --git a/pkg/services/ngalert/notifier/alertmanager_config.go b/pkg/services/ngalert/notifier/alertmanager_config.go index 7e755903791..ba8d37809b1 100644 --- a/pkg/services/ngalert/notifier/alertmanager_config.go +++ b/pkg/services/ngalert/notifier/alertmanager_config.go @@ -485,6 +485,7 @@ func assignReceiverConfigsUIDs(c []*definitions.PostableApiReceiver) error { type provisioningStore interface { GetProvenance(ctx context.Context, o models.Provisionable, org int64) (models.Provenance, error) GetProvenances(ctx context.Context, org int64, resourceType string) (map[string]models.Provenance, error) + GetProvenancesByUIDs(ctx context.Context, org int64, resourceType string, uids []string) (map[string]models.Provenance, error) SetProvenance(ctx context.Context, o models.Provisionable, org int64, p models.Provenance) error DeleteProvenance(ctx context.Context, o models.Provisionable, org int64) error } diff --git a/pkg/services/ngalert/provisioning/persist.go b/pkg/services/ngalert/provisioning/persist.go index 914a3644984..e6d6b37fc24 100644 --- a/pkg/services/ngalert/provisioning/persist.go +++ b/pkg/services/ngalert/provisioning/persist.go @@ -19,6 +19,7 @@ type alertmanagerConfigStore interface { type ProvisioningStore interface { GetProvenance(ctx context.Context, o models.Provisionable, org int64) (models.Provenance, error) GetProvenances(ctx context.Context, org int64, resourceType string) (map[string]models.Provenance, error) + GetProvenancesByUIDs(ctx context.Context, org int64, resourceType string, uids []string) (map[string]models.Provenance, error) SetProvenance(ctx context.Context, o models.Provisionable, org int64, p models.Provenance) error DeleteProvenance(ctx context.Context, o models.Provisionable, org int64) error } diff --git a/pkg/services/ngalert/provisioning/provisioning_store_mock.go b/pkg/services/ngalert/provisioning/provisioning_store_mock.go index 31cc77e26a6..bbc115d87c1 100644 --- a/pkg/services/ngalert/provisioning/provisioning_store_mock.go +++ b/pkg/services/ngalert/provisioning/provisioning_store_mock.go @@ -188,6 +188,67 @@ func (_c *MockProvisioningStore_GetProvenances_Call) RunAndReturn(run func(conte return _c } +// GetProvenancesByUIDs provides a mock function with given fields: ctx, org, resourceType, uids +func (_m *MockProvisioningStore) GetProvenancesByUIDs(ctx context.Context, org int64, resourceType string, uids []string) (map[string]models.Provenance, error) { + ret := _m.Called(ctx, org, resourceType, uids) + + if len(ret) == 0 { + panic("no return value specified for GetProvenancesByUIDs") + } + + var r0 map[string]models.Provenance + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, string, []string) (map[string]models.Provenance, error)); ok { + return rf(ctx, org, resourceType, uids) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, string, []string) map[string]models.Provenance); ok { + r0 = rf(ctx, org, resourceType, uids) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]models.Provenance) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, string, []string) error); ok { + r1 = rf(ctx, org, resourceType, uids) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockProvisioningStore_GetProvenancesByUIDs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetProvenancesByUIDs' +type MockProvisioningStore_GetProvenancesByUIDs_Call struct { + *mock.Call +} + +// GetProvenancesByUIDs is a helper method to define mock.On call +// - ctx context.Context +// - org int64 +// - resourceType string +// - uids []string +func (_e *MockProvisioningStore_Expecter) GetProvenancesByUIDs(ctx interface{}, org interface{}, resourceType interface{}, uids interface{}) *MockProvisioningStore_GetProvenancesByUIDs_Call { + return &MockProvisioningStore_GetProvenancesByUIDs_Call{Call: _e.mock.On("GetProvenancesByUIDs", ctx, org, resourceType, uids)} +} + +func (_c *MockProvisioningStore_GetProvenancesByUIDs_Call) Run(run func(ctx context.Context, org int64, resourceType string, uids []string)) *MockProvisioningStore_GetProvenancesByUIDs_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int64), args[2].(string), args[3].([]string)) + }) + return _c +} + +func (_c *MockProvisioningStore_GetProvenancesByUIDs_Call) Return(_a0 map[string]models.Provenance, _a1 error) *MockProvisioningStore_GetProvenancesByUIDs_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockProvisioningStore_GetProvenancesByUIDs_Call) RunAndReturn(run func(context.Context, int64, string, []string) (map[string]models.Provenance, error)) *MockProvisioningStore_GetProvenancesByUIDs_Call { + _c.Call.Return(run) + return _c +} + // SetProvenance provides a mock function with given fields: ctx, o, org, p func (_m *MockProvisioningStore) SetProvenance(ctx context.Context, o models.Provisionable, org int64, p models.Provenance) error { ret := _m.Called(ctx, o, org, p) diff --git a/pkg/services/ngalert/store/provisioning_store.go b/pkg/services/ngalert/store/provisioning_store.go index 27f03143c98..df5d7dc80c6 100644 --- a/pkg/services/ngalert/store/provisioning_store.go +++ b/pkg/services/ngalert/store/provisioning_store.go @@ -62,6 +62,30 @@ func (st DBstore) GetProvenances(ctx context.Context, org int64, resourceType st return resultMap, err } +// GetProvenancesByUIDs gets the provenance status for specific UIDs. +func (st DBstore) GetProvenancesByUIDs(ctx context.Context, org int64, resourceType string, uids []string) (map[string]models.Provenance, error) { + if len(uids) == 0 { + return map[string]models.Provenance{}, nil + } + + result := make(map[string]models.Provenance, len(uids)) + err := st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error { + rawData, err := sess.Table(provenanceRecord{}). + Where("record_type = ? AND org_id = ?", resourceType, org). + In("record_key", uids). + Cols("record_key", "provenance"). + QueryString() + if err != nil { + return fmt.Errorf("failed to query for existing provenance status: %w", err) + } + for _, data := range rawData { + result[data["record_key"]] = models.Provenance(data["provenance"]) + } + return nil + }) + return result, err +} + // SetProvenance changes the provenance status for a provisionable object. func (st DBstore) SetProvenance(ctx context.Context, o models.Provisionable, org int64, p models.Provenance) error { recordType := o.ResourceType() diff --git a/pkg/services/ngalert/store/provisioning_store_test.go b/pkg/services/ngalert/store/provisioning_store_test.go index b6f8b8fe5cd..359d594da0a 100644 --- a/pkg/services/ngalert/store/provisioning_store_test.go +++ b/pkg/services/ngalert/store/provisioning_store_test.go @@ -133,6 +133,55 @@ func TestIntegrationProvisioningStore(t *testing.T) { require.Equal(t, models.ProvenanceAPI, p[rule2.UID]) }) + t.Run("Store should return provenances by UIDs", func(t *testing.T) { + const orgID = 124 + rule1 := models.AlertRule{UID: "uid-1", OrgID: orgID} + rule2 := models.AlertRule{UID: "uid-2", OrgID: orgID} + rule3 := models.AlertRule{UID: "uid-3", OrgID: orgID} + + err := store.SetProvenance(context.Background(), &rule1, orgID, models.ProvenanceFile) + require.NoError(t, err) + err = store.SetProvenance(context.Background(), &rule2, orgID, models.ProvenanceAPI) + require.NoError(t, err) + err = store.SetProvenance(context.Background(), &rule3, orgID, models.ProvenanceFile) + require.NoError(t, err) + + // Fetch only rule1 and rule2 + p, err := store.GetProvenancesByUIDs(context.Background(), orgID, rule1.ResourceType(), []string{rule1.UID, rule2.UID}) + require.NoError(t, err) + require.Len(t, p, 2) + require.Equal(t, models.ProvenanceFile, p[rule1.UID]) + require.Equal(t, models.ProvenanceAPI, p[rule2.UID]) + _, exists := p[rule3.UID] + require.False(t, exists) + }) + + t.Run("GetProvenancesByUIDs returns empty map for empty UIDs", func(t *testing.T) { + p, err := store.GetProvenancesByUIDs(context.Background(), 1, "alertRule", []string{}) + require.NoError(t, err) + require.Empty(t, p) + }) + + t.Run("GetProvenancesByUIDs respects org ID", func(t *testing.T) { + const orgID1 = 125 + const orgID2 = 126 + rule := models.AlertRule{UID: "cross-org-uid"} + + err := store.SetProvenance(context.Background(), &rule, orgID1, models.ProvenanceFile) + require.NoError(t, err) + + // Should not find in different org + p, err := store.GetProvenancesByUIDs(context.Background(), orgID2, rule.ResourceType(), []string{rule.UID}) + require.NoError(t, err) + require.Empty(t, p) + + // Should find in correct org + p, err = store.GetProvenancesByUIDs(context.Background(), orgID1, rule.ResourceType(), []string{rule.UID}) + require.NoError(t, err) + require.Len(t, p, 1) + require.Equal(t, models.ProvenanceFile, p[rule.UID]) + }) + t.Run("Store should delete provenance correctly", func(t *testing.T) { const orgID = 1234 ruleOrg := models.AlertRule{ diff --git a/pkg/services/ngalert/tests/fakes/provisioning.go b/pkg/services/ngalert/tests/fakes/provisioning.go index 43de0a6dc68..fce2586f120 100644 --- a/pkg/services/ngalert/tests/fakes/provisioning.go +++ b/pkg/services/ngalert/tests/fakes/provisioning.go @@ -8,12 +8,13 @@ import ( ) type FakeProvisioningStore struct { - Calls []Call - Records map[int64]map[string]models.Provenance - GetProvenanceFunc func(ctx context.Context, o models.Provisionable, org int64) (models.Provenance, error) - GetProvenancesFunc func(ctx context.Context, orgID int64, resourceType string) (map[string]models.Provenance, error) - SetProvenanceFunc func(ctx context.Context, o models.Provisionable, org int64, p models.Provenance) error - DeleteProvenanceFunc func(ctx context.Context, o models.Provisionable, org int64) error + Calls []Call + Records map[int64]map[string]models.Provenance + GetProvenanceFunc func(ctx context.Context, o models.Provisionable, org int64) (models.Provenance, error) + GetProvenancesFunc func(ctx context.Context, orgID int64, resourceType string) (map[string]models.Provenance, error) + GetProvenancesByUIDsFunc func(ctx context.Context, orgID int64, resourceType string, uids []string) (map[string]models.Provenance, error) + SetProvenanceFunc func(ctx context.Context, o models.Provisionable, org int64, p models.Provenance) error + DeleteProvenanceFunc func(ctx context.Context, o models.Provisionable, org int64) error } func NewFakeProvisioningStore() *FakeProvisioningStore { @@ -51,6 +52,23 @@ func (f *FakeProvisioningStore) GetProvenances(ctx context.Context, orgID int64, return results, nil } +func (f *FakeProvisioningStore) GetProvenancesByUIDs(ctx context.Context, orgID int64, resourceType string, uids []string) (map[string]models.Provenance, error) { + f.Calls = append(f.Calls, Call{MethodName: "GetProvenancesByUIDs", Arguments: []any{ctx, orgID, resourceType, uids}}) + if f.GetProvenancesByUIDsFunc != nil { + return f.GetProvenancesByUIDsFunc(ctx, orgID, resourceType, uids) + } + results := make(map[string]models.Provenance) + if val, ok := f.Records[orgID]; ok { + for _, uid := range uids { + key := uid + resourceType + if prov, ok := val[key]; ok { + results[uid] = prov + } + } + } + return results, nil +} + func (f *FakeProvisioningStore) SetProvenance(ctx context.Context, o models.Provisionable, org int64, p models.Provenance) error { f.Calls = append(f.Calls, Call{MethodName: "SetProvenance", Arguments: []any{ctx, o, org, p}}) if f.SetProvenanceFunc != nil { From fa1e6cce5e217f01c93a8cdedc344bc8122b4eea Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Fri, 26 Dec 2025 16:55:57 -0500 Subject: [PATCH 293/297] Alerting: Rule backtesting with experimental UI (#115525) * add function to convert StateTransition to LokiEntry * add QueryResultBuilder * update backtesting to produce result similar to historian * make shouldRecord public * filter out noop transitions * add experimental front-end * add new fields * move conversion of api model to AlertRule to validation * add extra labels * calculate tick timestamp using the same logic as in scheduler * implement correct logic of calculating first evaluation timestamp * add uid, group and folder uid they are needed for jitter strategy * add JitterOffsetInDuration and JitterStrategy.String() * add config `backtesting_max_evaluations` to [unified_alerting] (not documented for now) * remove obsolete tests * elevate permisisons for backtesting endpoint * move backtesting to separate dir --- pkg/services/ngalert/api/api.go | 2 +- pkg/services/ngalert/api/api_testing.go | 60 +--- pkg/services/ngalert/api/authorization.go | 10 +- .../api/tooling/definitions/testing.go | 20 +- .../api/validation/api_ruler_validation.go | 57 ++- pkg/services/ngalert/backtesting/engine.go | 235 +++++++++--- .../ngalert/backtesting/engine_test.go | 338 ++++++++++-------- pkg/services/ngalert/backtesting/eval_data.go | 5 +- .../ngalert/backtesting/eval_data_test.go | 35 +- .../ngalert/backtesting/eval_query.go | 5 +- .../ngalert/backtesting/eval_query_test.go | 33 +- pkg/services/ngalert/models/alert_rule.go | 4 + pkg/services/ngalert/schedule/jitter.go | 10 + .../ngalert/schedule/ticker/ticker.go | 6 +- pkg/services/ngalert/state/historian/core.go | 6 +- .../ngalert/state/historian/core_test.go | 2 +- pkg/services/ngalert/state/historian/loki.go | 132 ++++--- pkg/setting/setting_unified_alerting.go | 7 + .../api/alerting/api_backtesting_test.go | 3 +- .../test-data/api_backtesting_data.json | 6 + .../alerting/unified/api/backtestApi.ts | 50 +++ .../backtesting/BacktestDropdownButton.tsx | 63 ++++ .../components/backtesting/BacktestPanel.tsx | 200 +++++++++++ .../alert-rule-form/AlertRuleForm.tsx | 3 + public/locales/en-US/grafana.json | 11 +- 25 files changed, 964 insertions(+), 339 deletions(-) create mode 100644 public/app/features/alerting/unified/api/backtestApi.ts create mode 100644 public/app/features/alerting/unified/components/backtesting/BacktestDropdownButton.tsx create mode 100644 public/app/features/alerting/unified/components/backtesting/BacktestPanel.tsx diff --git a/pkg/services/ngalert/api/api.go b/pkg/services/ngalert/api/api.go index eefbb6dea30..e2b60ad6e39 100644 --- a/pkg/services/ngalert/api/api.go +++ b/pkg/services/ngalert/api/api.go @@ -161,7 +161,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) { authz: ruleAuthzService, evaluator: api.EvaluatorFactory, cfg: &api.Cfg.UnifiedAlerting, - backtesting: backtesting.NewEngine(api.AppUrl, api.EvaluatorFactory, api.Tracer), + backtesting: backtesting.NewEngine(api.AppUrl, api.EvaluatorFactory, api.Tracer, api.Cfg.UnifiedAlerting, api.FeatureManager), featureManager: api.FeatureManager, appUrl: api.AppUrl, tracer: api.Tracer, diff --git a/pkg/services/ngalert/api/api_testing.go b/pkg/services/ngalert/api/api_testing.go index 3bda2e3f28f..13bc1a96c24 100644 --- a/pkg/services/ngalert/api/api_testing.go +++ b/pkg/services/ngalert/api/api_testing.go @@ -34,7 +34,6 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/state" "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util" ) type folderService interface { @@ -230,54 +229,27 @@ func (srv TestingApiSrv) BacktestAlertRule(c *contextmodel.ReqContext, cmd apimo return ErrResp(http.StatusNotFound, nil, "Backgtesting API is not enabled") } - if cmd.From.After(cmd.To) { - return ErrResp(400, nil, "From cannot be greater than To") - } - - noDataState, err := ngmodels.NoDataStateFromString(string(cmd.NoDataState)) - + rule, err := apivalidation.ValidateBacktestConfig(c.GetOrgID(), cmd, apivalidation.RuleLimitsFromConfig(srv.cfg, srv.featureManager)) if err != nil { - return ErrResp(400, err, "") - } - forInterval := time.Duration(cmd.For) - if forInterval < 0 { - return ErrResp(400, nil, "Bad For interval") + return ErrResp(http.StatusBadRequest, err, "") } - intervalSeconds, err := apivalidation.ValidateInterval(time.Duration(cmd.Interval), srv.cfg.BaseInterval) - if err != nil { - return ErrResp(400, err, "") - } - - queries := AlertQueriesFromApiAlertQueries(cmd.Data) - if err := srv.authz.AuthorizeDatasourceAccessForRule(c.Req.Context(), c.SignedInUser, &ngmodels.AlertRule{Data: queries}); err != nil { + if err := srv.authz.AuthorizeDatasourceAccessForRule(c.Req.Context(), c.SignedInUser, rule); err != nil { return errorToResponse(err) } - rule := &ngmodels.AlertRule{ - // ID: 0, - // Updated: time.Time{}, - // Version: 0, - // NamespaceUID: "", - // DashboardUID: nil, - // PanelID: nil, - // RuleGroup: "", - // RuleGroupIndex: 0, - // ExecErrState: "", - Title: cmd.Title, - // prefix backtesting- is to distinguish between executions of regular rule and backtesting in logs (like expression engine, evaluator, state manager etc) - UID: "backtesting-" + util.GenerateShortUID(), - OrgID: c.GetOrgID(), - Condition: cmd.Condition, - Data: queries, - IntervalSeconds: intervalSeconds, - NoDataState: noDataState, - For: forInterval, - Annotations: cmd.Annotations, - Labels: cmd.Labels, + // Fetch folder path for alert labels, fallback to "Backtesting" if not available + var folderTitle string + if cmd.NamespaceUID != "" { + f, err := srv.folderService.GetNamespaceByUID(c.Req.Context(), cmd.NamespaceUID, c.OrgID, c.SignedInUser) + if err != nil { + srv.log.FromContext(c.Req.Context()).Warn("Failed to fetch folder path for alert labels", "error", err) + } else { + folderTitle = f.Fullpath + } } - result, err := srv.backtesting.Test(c.Req.Context(), c.SignedInUser, rule, cmd.From, cmd.To) + result, err := srv.backtesting.Test(c.Req.Context(), c.SignedInUser, rule, cmd.From, cmd.To, folderTitle) if err != nil { if errors.Is(err, backtesting.ErrInvalidInputData) { return ErrResp(400, err, "Failed to evaluate") @@ -285,9 +257,5 @@ func (srv TestingApiSrv) BacktestAlertRule(c *contextmodel.ReqContext, cmd apimo return ErrResp(500, err, "Failed to evaluate") } - body, err := data.FrameToJSON(result, data.IncludeAll) - if err != nil { - return ErrResp(500, err, "Failed to convert frame to JSON") - } - return response.JSON(http.StatusOK, body) + return response.JSONStreaming(http.StatusOK, result) } diff --git a/pkg/services/ngalert/api/authorization.go b/pkg/services/ngalert/api/authorization.go index 1c107db22c6..7f8b42bb3a4 100644 --- a/pkg/services/ngalert/api/authorization.go +++ b/pkg/services/ngalert/api/authorization.go @@ -81,9 +81,15 @@ func (api *API) authorize(method, path string) web.Handler { // additional authorization is done in the request handler eval = ac.EvalPermission(ac.ActionAlertingRuleRead) // Grafana Rules Testing Paths - case http.MethodPost + "/api/v1/rule/backtest": + case http.MethodPost + "/api/v1/rule/backtest": // TODO (yuri) this should be protected by dedicated permission // additional authorization is done in the request handler - eval = ac.EvalPermission(ac.ActionAlertingRuleRead) + eval = ac.EvalAll( + ac.EvalPermission(ac.ActionAlertingRuleRead), + ac.EvalAny( + ac.EvalPermission(ac.ActionAlertingRuleUpdate), + ac.EvalPermission(ac.ActionAlertingRuleCreate), + ), + ) case http.MethodPost + "/api/v1/eval": // additional authorization is done in the request handler eval = ac.EvalPermission(ac.ActionAlertingRuleRead) diff --git a/pkg/services/ngalert/api/tooling/definitions/testing.go b/pkg/services/ngalert/api/tooling/definitions/testing.go index 2c228e94758..e094c8515b3 100644 --- a/pkg/services/ngalert/api/tooling/definitions/testing.go +++ b/pkg/services/ngalert/api/tooling/definitions/testing.go @@ -221,15 +221,21 @@ type BacktestConfig struct { To time.Time `json:"to"` Interval model.Duration `json:"interval,omitempty"` - Condition string `json:"condition"` - Data []AlertQuery `json:"data"` - For model.Duration `json:"for,omitempty"` + Condition string `json:"condition"` + Data []AlertQuery `json:"data"` + For *model.Duration `json:"for,omitempty"` + KeepFiringFor *model.Duration `json:"keep_firing_for,omitempty"` - Title string `json:"title"` - Labels map[string]string `json:"labels,omitempty"` - Annotations map[string]string `json:"annotations,omitempty"` + Title string `json:"title"` + Labels map[string]string `json:"labels,omitempty"` - NoDataState NoDataState `json:"no_data_state"` + NoDataState NoDataState `json:"no_data_state"` + ExecErrState ExecutionErrorState `json:"exec_err_state"` + MissingSeriesEvalsToResolve *int64 `json:"missing_series_evals_to_resolve,omitempty"` + + UID string `json:"uid,omitempty"` + RuleGroup string `json:"rule_group,omitempty"` + NamespaceUID string `json:"namespace_uid,omitempty"` } // swagger:model diff --git a/pkg/services/ngalert/api/validation/api_ruler_validation.go b/pkg/services/ngalert/api/validation/api_ruler_validation.go index 5a74c58f90e..09e601a4711 100644 --- a/pkg/services/ngalert/api/validation/api_ruler_validation.go +++ b/pkg/services/ngalert/api/validation/api_ruler_validation.go @@ -249,6 +249,21 @@ func ValidateCondition(condition string, queries []apimodels.AlertQuery, canPatc return nil } +func validateGroupInterval(incoming prommodels.Duration, limits RuleLimits) (time.Duration, error) { + interval := time.Duration(incoming) + if interval == 0 { + // if group interval is 0 (undefined) then we automatically fall back to the default interval + interval = limits.DefaultRuleEvaluationInterval + } + + if interval < 0 || int64(interval.Seconds())%int64(limits.BaseInterval.Seconds()) != 0 { + return 0, fmt.Errorf("rule evaluation interval (%d second) should be positive number that is multiple of the base interval of %d seconds", int64(interval.Seconds()), int64(limits.BaseInterval.Seconds())) + } + + // TODO should we validate that interval is >= cfg.MinInterval? Currently, we allow to save but fix the specified interval if it is < cfg.MinInterval + return interval, nil +} + func ValidateInterval(interval, baseInterval time.Duration) (int64, error) { intervalSeconds := int64(interval.Seconds()) @@ -336,18 +351,11 @@ func ValidateRuleGroup( return nil, fmt.Errorf("rule group name is too long. Max length is %d", store.AlertRuleMaxRuleGroupNameLength) } - interval := time.Duration(ruleGroupConfig.Interval) - if interval == 0 { - // if group interval is 0 (undefined) then we automatically fall back to the default interval - interval = limits.DefaultRuleEvaluationInterval + interval, err := validateGroupInterval(ruleGroupConfig.Interval, limits) + if err != nil { + return nil, err } - if interval < 0 || int64(interval.Seconds())%int64(limits.BaseInterval.Seconds()) != 0 { - return nil, fmt.Errorf("rule evaluation interval (%d second) should be positive number that is multiple of the base interval of %d seconds", int64(interval.Seconds()), int64(limits.BaseInterval.Seconds())) - } - - // TODO should we validate that interval is >= cfg.MinInterval? Currently, we allow to save but fix the specified interval if it is < cfg.MinInterval - // If the rule group is reserved for no-group rules, we cannot have multiple rules in it. if isNoGroupRuleGroup && len(ruleGroupConfig.Rules) > 1 { return nil, fmt.Errorf("rule group %s is reserved for no-group rules and cannot be used for rule groups with multiple rules", ruleGroupConfig.Name) @@ -410,3 +418,32 @@ func ValidateNotificationSettings(n *apimodels.AlertRuleNotificationSettings) ([ s, }, nil } + +func ValidateBacktestConfig(orgId int64, config apimodels.BacktestConfig, limits RuleLimits) (*ngmodels.AlertRule, error) { + if config.From.After(config.To) { + return nil, fmt.Errorf("invalid testing range: from %s must be before to %s", config.From, config.To) + } + + interval, err := validateGroupInterval(config.Interval, limits) + if err != nil { + return nil, err + } + + return ValidateRuleNode(&apimodels.PostableExtendedRuleNode{ + ApiRuleNode: &apimodels.ApiRuleNode{ + For: config.For, + KeepFiringFor: config.KeepFiringFor, + Labels: config.Labels, + Annotations: nil, + }, + GrafanaManagedAlert: &apimodels.PostableGrafanaRule{ + Title: config.Title, + Condition: config.Condition, + Data: config.Data, + UID: config.UID, + NoDataState: config.NoDataState, + ExecErrState: config.ExecErrState, + MissingSeriesEvalsToResolve: config.MissingSeriesEvalsToResolve, + }, + }, config.RuleGroup, interval, orgId, config.NamespaceUID, limits) +} diff --git a/pkg/services/ngalert/backtesting/engine.go b/pkg/services/ngalert/backtesting/engine.go index 31c968eb234..b4a534fe134 100644 --- a/pkg/services/ngalert/backtesting/engine.go +++ b/pkg/services/ngalert/backtesting/engine.go @@ -15,10 +15,16 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/schedule" + "github.com/grafana/grafana/pkg/services/ngalert/schedule/ticker" "github.com/grafana/grafana/pkg/services/ngalert/state" + "github.com/grafana/grafana/pkg/services/ngalert/state/historian" + history_model "github.com/grafana/grafana/pkg/services/ngalert/state/historian/model" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" ) var ( @@ -28,7 +34,7 @@ var ( backtestingEvaluatorFactory = newBacktestingEvaluator ) -type callbackFunc = func(evaluationIndex int, now time.Time, results eval.Results) error +type callbackFunc = func(evaluationIndex int, now time.Time, results eval.Results) (bool, error) type backtestingEvaluator interface { Eval(ctx context.Context, from time.Time, interval time.Duration, evaluations int, callback callbackFunc) error @@ -40,11 +46,17 @@ type stateManager interface { } type Engine struct { - evalFactory eval.EvaluatorFactory - createStateManager func() stateManager + evalFactory eval.EvaluatorFactory + createStateManager func() stateManager + disableGrafanaFolder bool + featureToggles featuremgmt.FeatureToggles + minInterval time.Duration + baseInterval time.Duration + jitterStrategy schedule.JitterStrategy + maxEvaluations int } -func NewEngine(appUrl *url.URL, evalFactory eval.EvaluatorFactory, tracer tracing.Tracer) *Engine { +func NewEngine(appUrl *url.URL, evalFactory eval.EvaluatorFactory, tracer tracing.Tracer, cfg setting.UnifiedAlertingSettings, toggles featuremgmt.FeatureToggles) *Engine { return &Engine{ evalFactory: evalFactory, createStateManager: func() stateManager { @@ -60,74 +72,139 @@ func NewEngine(appUrl *url.URL, evalFactory eval.EvaluatorFactory, tracer tracin } return state.NewManager(cfg, state.NewNoopPersister()) }, + disableGrafanaFolder: false, + featureToggles: toggles, + minInterval: cfg.MinInterval, + baseInterval: cfg.BaseInterval, + maxEvaluations: cfg.BacktestingMaxEvaluations, + jitterStrategy: schedule.JitterStrategyFrom(cfg, toggles), } } -func (e *Engine) Test(ctx context.Context, user identity.Requester, rule *models.AlertRule, from, to time.Time) (*data.Frame, error) { - ruleCtx := models.WithRuleKey(ctx, rule.GetKey()) - logger := logger.FromContext(ctx) - +func (e *Engine) Test(ctx context.Context, user identity.Requester, rule *models.AlertRule, from, to time.Time, folderTitle string) (res *data.Frame, err error) { + if rule == nil { + return nil, fmt.Errorf("%w: rule is not defined", ErrInvalidInputData) + } if !from.Before(to) { - return nil, fmt.Errorf("%w: invalid interval of the backtesting [%d,%d]", ErrInvalidInputData, from.Unix(), to.Unix()) + return nil, fmt.Errorf("%w: invalid interval [%d,%d]", ErrInvalidInputData, from.Unix(), to.Unix()) } - if to.Sub(from).Seconds() < float64(rule.IntervalSeconds) { - return nil, fmt.Errorf("%w: interval of the backtesting [%d,%d] is less than evaluation interval [%ds]", ErrInvalidInputData, from.Unix(), to.Unix(), rule.IntervalSeconds) + + ruleCtx := models.WithRuleKey(ctx, rule.GetKey()) + logger := logger.FromContext(ruleCtx).New("backtesting", util.GenerateShortUID()) + + var warns []string + if rule.GetInterval() < e.minInterval { + logger.Warn("Interval adjusted to minimal interval", "originalInterval", rule.GetInterval(), "adjustedInterval", e.minInterval) + rule = rule.Copy() + rule.IntervalSeconds = int64(e.minInterval.Seconds()) + warns = append(warns, fmt.Sprintf("Interval adjusted to minimal interval %ds", rule.IntervalSeconds)) } - length := int(to.Sub(from).Seconds()) / int(rule.IntervalSeconds) - stateManager := e.createStateManager() + effectiveStrategy := e.jitterStrategy + if e.jitterStrategy == schedule.JitterByGroup && (rule.RuleGroup == "" || rule.NamespaceUID == "") || + e.jitterStrategy == schedule.JitterByRule && rule.UID == "" { + logger.Warn(fmt.Sprintf("Jitter strategy is set to %s, but rule group or namespace is not set. Ignore jitter", e.jitterStrategy)) + warns = append(warns, fmt.Sprintf("Jitter strategy is set to %s, but rule group or namespace is not set. Ignore jitter. The results of testing will be different than real evaluations", e.jitterStrategy)) + effectiveStrategy = schedule.JitterNever + } + jitterOffset := schedule.JitterOffsetInDuration(rule, e.baseInterval, effectiveStrategy) + firstEval, err := getFirstEvaluationTime(from, rule, e.baseInterval, jitterOffset) + if err != nil { + return nil, fmt.Errorf("%w: %s", ErrInvalidInputData, err) + } - evaluator, err := backtestingEvaluatorFactory(ruleCtx, e.evalFactory, user, rule.GetEvalCondition().WithSource("backtesting"), &schedule.AlertingResultsFromRuleState{ - Manager: stateManager, - Rule: rule, - }) + evaluations := calculateNumberOfEvaluations(firstEval, to, rule.GetInterval()) + if e.maxEvaluations > 0 && evaluations > e.maxEvaluations { + logger.Warn("Evaluations adjusted to maximal number", "originalEvaluations", evaluations, "adjustedEvaluations", e.maxEvaluations) + warns = append(warns, fmt.Sprintf("Number of evaluations are adjusted to the limit of %d evaluations. Requested: %d", e.maxEvaluations, evaluations)) + evaluations = e.maxEvaluations + } + + start := time.Now() + defer func() { + if err == nil { + logger.Info("Rule testing finished successfully", "duration", time.Since(start)) + } else { + logger.Error("Rule testing finished with error", "duration", time.Since(start), "error", err) + } + }() + + stateMgr := e.createStateManager() + + evaluator, err := backtestingEvaluatorFactory(ruleCtx, + e.evalFactory, + user, + rule.GetEvalCondition().WithSource("backtesting"), + &schedule.AlertingResultsFromRuleState{ + Manager: stateMgr, + Rule: rule, + }, + ) if err != nil { return nil, errors.Join(ErrInvalidInputData, err) } - logger.Info("Start testing alert rule", "from", from, "to", to, "interval", rule.IntervalSeconds, "evaluations", length) + logger.Info("Start testing alert rule", "from", from, "to", to, "interval", rule.GetInterval(), "firstTick", firstEval, "evaluations", evaluations, "jitterOffset", jitterOffset, "jitterStrategy", effectiveStrategy) - start := time.Now() + var builder *historian.QueryResultBuilder - tsField := data.NewField("Time", nil, make([]time.Time, length)) - valueFields := make(map[data.Fingerprint]*data.Field) - - err = evaluator.Eval(ruleCtx, from, time.Duration(rule.IntervalSeconds)*time.Second, length, func(idx int, currentTime time.Time, results eval.Results) error { - if idx >= length { - logger.Info("Unexpected evaluation. Skipping", "from", from, "to", to, "interval", rule.IntervalSeconds, "evaluationTime", currentTime, "evaluationIndex", idx, "expectedEvaluations", length) - return nil - } - states := stateManager.ProcessEvalResults(ruleCtx, currentTime, rule, results, nil, nil) - tsField.Set(idx, currentTime) - for _, s := range states { - field, ok := valueFields[s.CacheID] - if !ok { - field = data.NewField("", s.Labels, make([]*string, length)) - valueFields[s.CacheID] = field - } - if s.State.State != eval.NoData { // set nil if NoData - value := s.State.State.String() - if s.StateReason != "" { - value += " (" + s.StateReason + ")" - } - field.Set(idx, &value) - continue - } - } - return nil - }) - fields := make([]*data.Field, 0, len(valueFields)+1) - fields = append(fields, tsField) - for _, f := range valueFields { - fields = append(fields, f) + ruleMeta := history_model.RuleMeta{ + ID: rule.ID, + OrgID: rule.OrgID, + UID: rule.UID, + Title: rule.Title, + Group: rule.RuleGroup, + NamespaceUID: rule.NamespaceUID, + // DashboardUID: "", + // PanelID: 0, + Condition: rule.Condition, } - result := data.NewFrame("Testing results", fields...) - + labels := map[string]string{ + historian.OrgIDLabel: fmt.Sprint(ruleMeta.OrgID), + historian.GroupLabel: fmt.Sprint(ruleMeta.Group), + historian.FolderUIDLabel: fmt.Sprint(rule.NamespaceUID), + } + labelsBytes, err := json.Marshal(labels) if err != nil { return nil, err } - logger.Info("Rule testing finished successfully", "duration", time.Since(start)) - return result, nil + + // Ensure fallback if empty string is passed + if folderTitle == "" { + folderTitle = "Backtesting" + } + extraLabels := state.GetRuleExtraLabels(logger, rule, folderTitle, !e.disableGrafanaFolder, e.featureToggles) + + processFn := func(idx int, currentTime time.Time, results eval.Results) (bool, error) { + // init the builder. Do the best guess for the size of the result + if builder == nil { + builder = historian.NewQueryResultBuilder(evaluations * len(results)) + for _, warn := range warns { + builder.AddWarn(warn) + } + } + states := stateMgr.ProcessEvalResults(ruleCtx, currentTime, rule, results, extraLabels, nil) + for _, s := range states { + if !historian.ShouldRecord(s) { + continue + } + entry := historian.StateTransitionToLokiEntry(ruleMeta, s) + err := builder.AddRow(currentTime, entry, labelsBytes) + if err != nil { + return false, err + } + } + return idx <= evaluations, nil + } + + err = evaluator.Eval(ruleCtx, firstEval, rule.GetInterval(), evaluations, processFn) + if err != nil { + return nil, err + } + if builder == nil { + return nil, errors.New("no results were produced") + } + return builder.ToFrame(), nil } func newBacktestingEvaluator(ctx context.Context, evalFactory eval.EvaluatorFactory, user identity.Requester, condition models.Condition, reader eval.AlertingResultsReader) (backtestingEvaluator, error) { @@ -173,3 +250,53 @@ type NoopImageService struct{} func (s *NoopImageService) NewImage(_ context.Context, _ *models.AlertRule) (*models.Image, error) { return &models.Image{}, nil } + +func getNextEvaluationTime(currentTime time.Time, rule *models.AlertRule, baseInterval time.Duration, jitterOffset time.Duration) (time.Time, error) { + if rule.IntervalSeconds%int64(baseInterval.Seconds()) != 0 { + return time.Time{}, fmt.Errorf("interval %ds is not divisible by base interval %ds", rule.IntervalSeconds, int64(baseInterval.Seconds())) + } + + freq := rule.IntervalSeconds / int64(baseInterval.Seconds()) + + firstTickNum := currentTime.Unix() / int64(baseInterval.Seconds()) + + jitterOffsetTicks := int64(jitterOffset / baseInterval) + + firstEvalTickNum := firstTickNum + (jitterOffsetTicks-(firstTickNum%freq)+freq)%freq + + return time.Unix(firstEvalTickNum*int64(baseInterval.Seconds()), 0), nil +} + +func getFirstEvaluationTime(from time.Time, rule *models.AlertRule, baseInterval time.Duration, jitterOffset time.Duration) (time.Time, error) { + // Now calculate the time of the tick the same way as in the scheduler + firstTick := ticker.GetStartTick(from, baseInterval) + + // calculate time of the first evaluation that is at or after the first tick + firstEval, err := getNextEvaluationTime(firstTick, rule, baseInterval, jitterOffset) + if err != nil { + return time.Time{}, err + } + + // Ensure firstEval is at or after from + // Calculate how many intervals to skip to get past 'from' + if firstEval.Before(from) { + diff := from.Sub(firstEval) + interval := rule.GetInterval() + // Ceiling division: how many intervals needed to cover the difference + intervalsToAdd := (diff + interval - 1) / interval + firstEval = firstEval.Add(interval * intervalsToAdd) + } + + return firstEval, nil +} + +func calculateNumberOfEvaluations(firstEval, to time.Time, interval time.Duration) int { + var evaluations int + if to.After(firstEval) { + evaluations = int(to.Sub(firstEval).Seconds()) / int(interval.Seconds()) + } + if evaluations == 0 { + evaluations = 1 + } + return evaluations +} diff --git a/pkg/services/ngalert/backtesting/engine_test.go b/pkg/services/ngalert/backtesting/engine_test.go index d2685e71535..33441d73f32 100644 --- a/pkg/services/ngalert/backtesting/engine_test.go +++ b/pkg/services/ngalert/backtesting/engine_test.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "errors" - "fmt" "math/rand" "testing" "time" @@ -14,9 +13,11 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/eval/eval_mocks" "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/services/ngalert/schedule" "github.com/grafana/grafana/pkg/services/ngalert/state" "github.com/grafana/grafana/pkg/util" ) @@ -158,16 +159,6 @@ func TestNewBacktestingEvaluator(t *testing.T) { } func TestEvaluatorTest(t *testing.T) { - states := []eval.State{eval.Normal, eval.Alerting, eval.Pending} - generateState := func(prefix string) *state.State { - labels := models.GenerateAlertLabels(rand.Intn(5)+1, prefix+"-") - return &state.State{ - CacheID: labels.Fingerprint(), - Labels: labels, - State: states[rand.Intn(len(states))], - } - } - randomResultCallback := func(now time.Time) (eval.Results, error) { return eval.GenerateResults(rand.Intn(5)+1, eval.ResultGen()), nil } @@ -189,84 +180,17 @@ func TestEvaluatorTest(t *testing.T) { createStateManager: func() stateManager { return manager }, + disableGrafanaFolder: false, + featureToggles: featuremgmt.WithFeatures(), + minInterval: 1 * time.Second, + baseInterval: 1 * time.Second, + jitterStrategy: schedule.JitterNever, + maxEvaluations: 10000, } gen := models.RuleGen rule := gen.With(gen.WithInterval(time.Second)).GenerateRef() ruleInterval := time.Duration(rule.IntervalSeconds) * time.Second - t.Run("should return data frame in specific format", func(t *testing.T) { - from := time.Unix(0, 0) - to := from.Add(5 * ruleInterval) - allStates := [...]eval.State{eval.Normal, eval.Alerting, eval.Pending, eval.NoData, eval.Error} - - var states []state.StateTransition - - for _, s := range allStates { - labels := models.GenerateAlertLabels(rand.Intn(5)+1, s.String()+"-") - states = append(states, state.StateTransition{ - State: &state.State{ - CacheID: labels.Fingerprint(), - Labels: labels, - State: s, - StateReason: util.GenerateShortUID(), - }, - }) - } - - manager.stateCallback = func(now time.Time) []state.StateTransition { - return states - } - - frame, err := engine.Test(context.Background(), nil, rule, from, to) - - require.NoError(t, err) - require.Len(t, frame.Fields, len(states)+1) // +1 - timestamp - - t.Run("should contain field Time", func(t *testing.T) { - timestampField, _ := frame.FieldByName("Time") - require.NotNil(t, timestampField, "frame does not contain field 'Time'") - require.Equal(t, data.FieldTypeTime, timestampField.Type()) - }) - - fieldByState := make(map[data.Fingerprint]*data.Field, len(states)) - - t.Run("should contain a field per state", func(t *testing.T) { - for _, s := range states { - var f *data.Field - for _, field := range frame.Fields { - if field.Labels.String() == s.Labels.String() { - f = field - break - } - } - require.NotNilf(t, f, "Cannot find a field by state labels") - fieldByState[s.CacheID] = f - } - }) - - t.Run("should be populated with correct values", func(t *testing.T) { - timestampField, _ := frame.FieldByName("Time") - expectedLength := timestampField.Len() - for _, field := range frame.Fields { - require.Equalf(t, expectedLength, field.Len(), "Field %s should have the size %d", field.Name, expectedLength) - } - for i := 0; i < expectedLength; i++ { - expectedTime := from.Add(time.Duration(int64(i)*rule.IntervalSeconds) * time.Second) - require.Equal(t, expectedTime, timestampField.At(i).(time.Time)) - for _, s := range states { - f := fieldByState[s.CacheID] - if s.State.State == eval.NoData { - require.Nil(t, f.At(i)) - } else { - v := f.At(i).(*string) - require.NotNilf(t, v, "Field [%s] value at index %d should not be nil", s.CacheID, i) - require.Equal(t, fmt.Sprintf("%s (%s)", s.State.State, s.StateReason), *v) - } - } - } - }) - }) - t.Run("should not fail if 'to-from' is not times of interval", func(t *testing.T) { from := time.Unix(0, 0) to := from.Add(5 * ruleInterval) @@ -287,84 +211,26 @@ func TestEvaluatorTest(t *testing.T) { return states } - frame, err := engine.Test(context.Background(), nil, rule, from, to) + frame, err := engine.Test(context.Background(), nil, rule, from, to, "") require.NoError(t, err) expectedLen := frame.Rows() for i := 0; i < 100; i++ { jitter := time.Duration(rand.Int63n(ruleInterval.Milliseconds())) * time.Millisecond - frame, err = engine.Test(context.Background(), nil, rule, from, to.Add(jitter)) + frame, err = engine.Test(context.Background(), nil, rule, from, to.Add(jitter), "") require.NoError(t, err) require.Equalf(t, expectedLen, frame.Rows(), "jitter %v caused result to be different that base-line", jitter) } }) - t.Run("should backfill field with nulls if a new dimension created in the middle", func(t *testing.T) { - from := time.Unix(0, 0) - - state1 := state.StateTransition{ - State: generateState("1"), - } - state2 := state.StateTransition{ - State: generateState("2"), - } - state3 := state.StateTransition{ - State: generateState("3"), - } - stateByTime := map[time.Time][]state.StateTransition{ - from: {state1, state2}, - from.Add(1 * ruleInterval): {state1, state2}, - from.Add(2 * ruleInterval): {state1, state2}, - from.Add(3 * ruleInterval): {state1, state2, state3}, - from.Add(4 * ruleInterval): {state1, state2, state3}, - } - to := from.Add(time.Duration(len(stateByTime)) * ruleInterval) - - manager.stateCallback = func(now time.Time) []state.StateTransition { - return stateByTime[now] - } - - frame, err := engine.Test(context.Background(), nil, rule, from, to) - require.NoError(t, err) - - var field3 *data.Field - for _, field := range frame.Fields { - if field.Labels.String() == state3.Labels.String() { - field3 = field - break - } - } - require.NotNilf(t, field3, "Result for state 3 was not found") - require.Equalf(t, len(stateByTime), field3.Len(), "State3 result has unexpected number of values") - - idx := 0 - for curTime, states := range stateByTime { - value := field3.At(idx).(*string) - if len(states) == 2 { - require.Nilf(t, value, "The result should be nil if state3 was not available for time %v", curTime) - } - } - }) - t.Run("should fail", func(t *testing.T) { manager.stateCallback = func(now time.Time) []state.StateTransition { return nil } - t.Run("when interval is not correct", func(t *testing.T) { from := time.Now() - t.Run("when from=to", func(t *testing.T) { - to := from - _, err := engine.Test(context.Background(), nil, rule, from, to) - require.ErrorIs(t, err, ErrInvalidInputData) - }) t.Run("when from > to", func(t *testing.T) { to := from.Add(-ruleInterval) - _, err := engine.Test(context.Background(), nil, rule, from, to) - require.ErrorIs(t, err, ErrInvalidInputData) - }) - t.Run("when to-from < interval", func(t *testing.T) { - to := from.Add(ruleInterval).Add(-time.Millisecond) - _, err := engine.Test(context.Background(), nil, rule, from, to) + _, err := engine.Test(context.Background(), nil, rule, from, to, "") require.ErrorIs(t, err, ErrInvalidInputData) }) }) @@ -376,7 +242,7 @@ func TestEvaluatorTest(t *testing.T) { } from := time.Now() to := from.Add(ruleInterval) - _, err := engine.Test(context.Background(), nil, rule, from, to) + _, err := engine.Test(context.Background(), nil, rule, from, to, "") require.ErrorIs(t, err, expectedError) }) }) @@ -404,10 +270,188 @@ func (f *fakeBacktestingEvaluator) Eval(_ context.Context, from time.Time, inter if err != nil { return err } - err = callback(idx, now, results) + c, err := callback(idx, now, results) if err != nil { return err } + if !c { + break + } } return nil } + +func TestGetNextEvaluationTime(t *testing.T) { + baseInterval := 10 * time.Second + + testCases := []struct { + name string + ruleInterval int64 + currentTimestamp int64 + jitterOffset time.Duration + expectError bool + expectedNext int64 + }{ + { + name: "interval not divisible by base interval", + ruleInterval: 15, + currentTimestamp: 0, + jitterOffset: 0, + expectError: true, + }, + { + name: "no jitter - from tick 0", + ruleInterval: 20, + currentTimestamp: 0, + jitterOffset: 0, + expectedNext: 0, + }, + { + name: "no jitter - from tick 1", + ruleInterval: 20, + currentTimestamp: 10, + jitterOffset: 0, + expectedNext: 20, + }, + { + name: "no jitter - from tick 2", + ruleInterval: 20, + currentTimestamp: 20, + jitterOffset: 0, + expectedNext: 20, + }, + { + name: "with 20s jitter - from tick 0", + ruleInterval: 60, + currentTimestamp: 0, + jitterOffset: 20 * time.Second, + expectedNext: 20, + }, + { + name: "with 20s jitter - from tick 2", + ruleInterval: 60, + currentTimestamp: 20, + jitterOffset: 20 * time.Second, + expectedNext: 20, + }, + { + name: "with 20s jitter - from tick 3", + ruleInterval: 60, + currentTimestamp: 30, + jitterOffset: 20 * time.Second, + expectedNext: 80, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + rule := &models.AlertRule{IntervalSeconds: tc.ruleInterval} + currentTime := time.Unix(tc.currentTimestamp, 0) + result, err := getNextEvaluationTime(currentTime, rule, baseInterval, tc.jitterOffset) + + if tc.expectError { + require.Error(t, err) + require.Contains(t, err.Error(), "is not divisible by base interval") + return + } + + require.NoError(t, err) + require.Equal(t, tc.expectedNext, result.Unix()) + }) + } +} + +func TestGetFirstEvaluationTime(t *testing.T) { + baseInterval := 10 * time.Second + + testCases := []struct { + name string + ruleInterval int64 + fromUnix int64 + jitterOffset time.Duration + expectError bool + expectedUnix int64 + }{ + { + name: "interval not divisible by base interval", + ruleInterval: 15, + fromUnix: 0, + jitterOffset: 0, + expectError: true, + }, + { + name: "no jitter - from at tick 0", + ruleInterval: 20, + fromUnix: 0, + jitterOffset: 0, + expectedUnix: 0, + }, + { + name: "no jitter - from at tick 1", + ruleInterval: 20, + fromUnix: 10, + jitterOffset: 0, + expectedUnix: 20, + }, + { + name: "no jitter - from before first tick", + ruleInterval: 20, + fromUnix: 5, + jitterOffset: 0, + expectedUnix: 20, + }, + { + name: "no jitter - from after first aligned tick", + ruleInterval: 20, + fromUnix: 25, + jitterOffset: 0, + expectedUnix: 40, + }, + { + name: "no jitter - from at tick boundary", + ruleInterval: 10, + fromUnix: 10, + jitterOffset: 0, + expectedUnix: 10, + }, + { + name: "with 20s jitter - from epoch", + ruleInterval: 60, + fromUnix: 0, + jitterOffset: 20 * time.Second, + expectedUnix: 20, + }, + { + name: "with 20s jitter - from 70s", + ruleInterval: 60, + fromUnix: 70, + jitterOffset: 20 * time.Second, + expectedUnix: 80, + }, + { + name: "with 50s jitter - from 25s", + ruleInterval: 60, + fromUnix: 25, + jitterOffset: 50 * time.Second, + expectedUnix: 50, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + rule := &models.AlertRule{IntervalSeconds: tc.ruleInterval} + from := time.Unix(tc.fromUnix, 0) + result, err := getFirstEvaluationTime(from, rule, baseInterval, tc.jitterOffset) + + if tc.expectError { + require.Error(t, err) + require.Contains(t, err.Error(), "is not divisible by base interval") + return + } + + require.NoError(t, err) + require.Equal(t, tc.expectedUnix, result.Unix()) + require.GreaterOrEqual(t, result.Unix(), from.Unix(), "first eval should be at or after from") + }) + } +} diff --git a/pkg/services/ngalert/backtesting/eval_data.go b/pkg/services/ngalert/backtesting/eval_data.go index 999c0bc6302..13827e6e757 100644 --- a/pkg/services/ngalert/backtesting/eval_data.go +++ b/pkg/services/ngalert/backtesting/eval_data.go @@ -85,10 +85,13 @@ func (d *dataEvaluator) Eval(_ context.Context, from time.Time, interval time.Du EvaluatedAt: now, }) } - err := callback(i, now, result) + cont, err := callback(i, now, result) if err != nil { return err } + if !cont { + break + } } return nil } diff --git a/pkg/services/ngalert/backtesting/eval_data_test.go b/pkg/services/ngalert/backtesting/eval_data_test.go index 864229b777c..3d80fa9337a 100644 --- a/pkg/services/ngalert/backtesting/eval_data_test.go +++ b/pkg/services/ngalert/backtesting/eval_data_test.go @@ -100,11 +100,11 @@ func TestDataEvaluator_Eval(t *testing.T) { resultsCount := int(to.Sub(from).Seconds() / interval.Seconds()) - err = evaluator.Eval(context.Background(), from, time.Second, resultsCount, func(idx int, now time.Time, res eval.Results) error { + err = evaluator.Eval(context.Background(), from, time.Second, resultsCount, func(idx int, now time.Time, res eval.Results) (bool, error) { r = append(r, results{ now, res, }) - return nil + return true, nil }) require.NoError(t, err) @@ -164,11 +164,11 @@ func TestDataEvaluator_Eval(t *testing.T) { size := to.Sub(from).Milliseconds() / interval.Milliseconds() r := make([]results, 0, size) - err = evaluator.Eval(context.Background(), from, interval, int(size), func(idx int, now time.Time, res eval.Results) error { + err = evaluator.Eval(context.Background(), from, interval, int(size), func(idx int, now time.Time, res eval.Results) (bool, error) { r = append(r, results{ now, res, }) - return nil + return true, nil }) currentRowIdx := 0 @@ -195,11 +195,11 @@ func TestDataEvaluator_Eval(t *testing.T) { size := int(to.Sub(from).Seconds() / interval.Seconds()) r := make([]results, 0, size) - err = evaluator.Eval(context.Background(), from, interval, size, func(idx int, now time.Time, res eval.Results) error { + err = evaluator.Eval(context.Background(), from, interval, size, func(idx int, now time.Time, res eval.Results) (bool, error) { r = append(r, results{ now, res, }) - return nil + return true, nil }) currentRowIdx := 0 @@ -230,11 +230,11 @@ func TestDataEvaluator_Eval(t *testing.T) { t.Run("should be noData until the frame interval", func(t *testing.T) { newFrom := from.Add(-10 * time.Second) r := make([]results, 0, int(to.Sub(newFrom).Seconds())) - err = evaluator.Eval(context.Background(), newFrom, time.Second, cap(r), func(idx int, now time.Time, res eval.Results) error { + err = evaluator.Eval(context.Background(), newFrom, time.Second, cap(r), func(idx int, now time.Time, res eval.Results) (bool, error) { r = append(r, results{ now, res, }) - return nil + return true, nil }) rowIdx := 0 @@ -258,11 +258,11 @@ func TestDataEvaluator_Eval(t *testing.T) { t.Run("should be the last value after the frame interval", func(t *testing.T) { newTo := to.Add(10 * time.Second) r := make([]results, 0, int(newTo.Sub(from).Seconds())) - err = evaluator.Eval(context.Background(), from, time.Second, cap(r), func(idx int, now time.Time, res eval.Results) error { + err = evaluator.Eval(context.Background(), from, time.Second, cap(r), func(idx int, now time.Time, res eval.Results) (bool, error) { r = append(r, results{ now, res, }) - return nil + return true, nil }) rowIdx := 0 @@ -282,12 +282,21 @@ func TestDataEvaluator_Eval(t *testing.T) { }) t.Run("should stop if callback error", func(t *testing.T) { expectedError := errors.New("error") - err = evaluator.Eval(context.Background(), from, time.Second, 6, func(idx int, now time.Time, res eval.Results) error { + err = evaluator.Eval(context.Background(), from, time.Second, 6, func(idx int, now time.Time, res eval.Results) (bool, error) { if idx == 5 { - return expectedError + return false, expectedError } - return nil + return true, nil }) require.ErrorIs(t, err, expectedError) }) + t.Run("should stop if callback does not want to continue", func(t *testing.T) { + evaluated := 0 + err = evaluator.Eval(context.Background(), from, time.Second, 6, func(idx int, now time.Time, res eval.Results) (bool, error) { + evaluated++ + return evaluated < 2, nil + }) + require.NoError(t, err) + require.Equal(t, 2, evaluated) + }) } diff --git a/pkg/services/ngalert/backtesting/eval_query.go b/pkg/services/ngalert/backtesting/eval_query.go index f53e3de86cb..07720f4f265 100644 --- a/pkg/services/ngalert/backtesting/eval_query.go +++ b/pkg/services/ngalert/backtesting/eval_query.go @@ -18,10 +18,13 @@ func (d *queryEvaluator) Eval(ctx context.Context, from time.Time, interval time if err != nil { return err } - err = callback(idx, now, results) + cont, err := callback(idx, now, results) if err != nil { return err } + if !cont { + break + } } return nil } diff --git a/pkg/services/ngalert/backtesting/eval_query_test.go b/pkg/services/ngalert/backtesting/eval_query_test.go index e88948971f0..4c9df9d25b1 100644 --- a/pkg/services/ngalert/backtesting/eval_query_test.go +++ b/pkg/services/ngalert/backtesting/eval_query_test.go @@ -31,9 +31,9 @@ func TestQueryEvaluator_Eval(t *testing.T) { intervals := make([]time.Time, times) - err := evaluator.Eval(ctx, from, interval, times, func(idx int, now time.Time, results eval.Results) error { + err := evaluator.Eval(ctx, from, interval, times, func(idx int, now time.Time, results eval.Results) (bool, error) { intervals[idx] = now - return nil + return true, nil }) require.NoError(t, err) require.Len(t, intervals, times) @@ -49,7 +49,7 @@ func TestQueryEvaluator_Eval(t *testing.T) { } }) - t.Run("should stop evaluation if error", func(t *testing.T) { + t.Run("should stop evaluation", func(t *testing.T) { t.Run("when evaluation fails", func(t *testing.T) { m := &eval_mocks.ConditionEvaluatorMock{} expectedResults := eval.Results{} @@ -62,9 +62,9 @@ func TestQueryEvaluator_Eval(t *testing.T) { intervals := make([]time.Time, 0, times) - err := evaluator.Eval(ctx, from, interval, times, func(idx int, now time.Time, results eval.Results) error { + err := evaluator.Eval(ctx, from, interval, times, func(idx int, now time.Time, results eval.Results) (bool, error) { intervals = append(intervals, now) - return nil + return true, nil }) require.ErrorIs(t, err, expectedError) require.Len(t, intervals, 3) @@ -81,14 +81,31 @@ func TestQueryEvaluator_Eval(t *testing.T) { intervals := make([]time.Time, 0, times) - err := evaluator.Eval(ctx, from, interval, times, func(idx int, now time.Time, results eval.Results) error { + err := evaluator.Eval(ctx, from, interval, times, func(idx int, now time.Time, results eval.Results) (bool, error) { if len(intervals) > 3 { - return expectedError + return false, expectedError } intervals = append(intervals, now) - return nil + return true, nil }) require.ErrorIs(t, err, expectedError) }) + + t.Run("when callback does not want to continue", func(t *testing.T) { + m := &eval_mocks.ConditionEvaluatorMock{} + expectedResults := eval.Results{} + m.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(expectedResults, nil) + evaluator := queryEvaluator{ + eval: m, + } + + evaluated := 0 + err := evaluator.Eval(ctx, from, interval, times, func(idx int, now time.Time, results eval.Results) (bool, error) { + evaluated++ + return evaluated <= 2, nil + }) + require.NoError(t, err, nil) + require.Equal(t, 3, evaluated) + }) }) } diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index f7bb3d9fcfd..7d6e8a1fab5 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -480,6 +480,10 @@ func (alertRule *AlertRule) GetPanelID() int64 { return -1 } +func (alertRule *AlertRule) GetInterval() time.Duration { + return time.Duration(alertRule.IntervalSeconds) * time.Second +} + type LabelOption func(map[string]string) func WithoutInternalLabels() LabelOption { diff --git a/pkg/services/ngalert/schedule/jitter.go b/pkg/services/ngalert/schedule/jitter.go index 3d6c839f372..a805ab9b0b3 100644 --- a/pkg/services/ngalert/schedule/jitter.go +++ b/pkg/services/ngalert/schedule/jitter.go @@ -5,6 +5,7 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/services/featuremgmt" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/setting" @@ -13,6 +14,10 @@ import ( // JitterStrategy represents a modifier to alert rule timing that affects how evaluations are distributed. type JitterStrategy int +func (s JitterStrategy) String() string { + return [...]string{"never", "by group", "by rule"}[s] +} + const ( JitterNever JitterStrategy = iota JitterByGroup @@ -57,6 +62,11 @@ func jitterOffsetInTicks(r *ngmodels.AlertRule, baseInterval time.Duration, stra return res } +// JitterOffsetInDuration gives the jitter offset for a rule, in terms of a duration relative to its interval and a base interval. +func JitterOffsetInDuration(r *ngmodels.AlertRule, baseInterval time.Duration, strategy JitterStrategy) time.Duration { + return time.Duration(jitterOffsetInTicks(r, baseInterval, strategy)) * baseInterval +} + func jitterHash(r *ngmodels.AlertRule, strategy JitterStrategy) uint64 { ls := data.Labels{ "name": r.RuleGroup, diff --git a/pkg/services/ngalert/schedule/ticker/ticker.go b/pkg/services/ngalert/schedule/ticker/ticker.go index a52b9c4e559..c24dc13798a 100644 --- a/pkg/services/ngalert/schedule/ticker/ticker.go +++ b/pkg/services/ngalert/schedule/ticker/ticker.go @@ -44,7 +44,11 @@ func New(c clock.Clock, interval time.Duration, metric *Metrics, logger log.Logg } func getStartTick(clk clock.Clock, interval time.Duration) time.Time { - nano := clk.Now().UnixNano() + return GetStartTick(clk.Now(), interval) +} + +func GetStartTick(t time.Time, interval time.Duration) time.Time { + nano := t.UnixNano() return time.Unix(0, nano-(nano%interval.Nanoseconds())) } diff --git a/pkg/services/ngalert/state/historian/core.go b/pkg/services/ngalert/state/historian/core.go index eabb5214a9e..daf415be25a 100644 --- a/pkg/services/ngalert/state/historian/core.go +++ b/pkg/services/ngalert/state/historian/core.go @@ -17,7 +17,7 @@ import ( const StateHistoryWriteTimeout = time.Minute -func shouldRecord(transition state.StateTransition) bool { +func ShouldRecord(transition state.StateTransition) bool { if !transition.Changed() { return false } @@ -35,9 +35,9 @@ func shouldRecord(transition state.StateTransition) bool { } // ShouldRecordAnnotation returns true if an annotation should be created for a given state transition. -// This is stricter than shouldRecord to avoid cluttering panels with state transitions. +// This is stricter than ShouldRecord to avoid cluttering panels with state transitions. func ShouldRecordAnnotation(t state.StateTransition) bool { - if !shouldRecord(t) { + if !ShouldRecord(t) { return false } diff --git a/pkg/services/ngalert/state/historian/core_test.go b/pkg/services/ngalert/state/historian/core_test.go index f798bd26cef..a5f0c55d816 100644 --- a/pkg/services/ngalert/state/historian/core_test.go +++ b/pkg/services/ngalert/state/historian/core_test.go @@ -92,7 +92,7 @@ func TestShouldRecord(t *testing.T) { } t.Run(fmt.Sprintf("%s -> %s should be %v", trans.PreviousFormatted(), trans.Formatted(), !ok), func(t *testing.T) { - require.Equal(t, !ok, shouldRecord(trans)) + require.Equal(t, !ok, ShouldRecord(trans)) }) } } diff --git a/pkg/services/ngalert/state/historian/loki.go b/pkg/services/ngalert/state/historian/loki.go index 76e3e9025bd..8e98d411c1e 100644 --- a/pkg/services/ngalert/state/historian/loki.go +++ b/pkg/services/ngalert/state/historian/loki.go @@ -41,6 +41,69 @@ const ( dfLabels = "labels" ) +// QueryResultBuilder is a builder for a data frame that represents query results from Loki. +// It contains three fields: time (timestamp), line (JSON data), and labels (JSON labels). +type QueryResultBuilder struct { + frame *data.Frame +} + +// NewQueryResultBuilder creates a new QueryResultBuilder with the specified capacity. +// The capacity is used to pre-allocate the underlying slices for better performance. +func NewQueryResultBuilder(capacity int) *QueryResultBuilder { + frame := data.NewFrame("states") + lbls := data.Labels(map[string]string{}) + + // We represent state history as a single merged history, that roughly corresponds to what you get in the Grafana Explore tab when querying Loki directly. + // The format is composed of the following vectors: + // 1. `time` - timestamp - when the transition happened + // 2. `line` - JSON - the full data of the transition + // 3. `labels` - JSON - the labels associated with that state transition + times := make([]time.Time, 0, capacity) + lines := make([]json.RawMessage, 0, capacity) + labels := make([]json.RawMessage, 0, capacity) + + frame.Fields = append(frame.Fields, data.NewField(dfTime, lbls, times)) + frame.Fields = append(frame.Fields, data.NewField(dfLine, lbls, lines)) + frame.Fields = append(frame.Fields, data.NewField(dfLabels, lbls, labels)) + + return &QueryResultBuilder{frame: frame} +} + +func (qr QueryResultBuilder) AddRowRaw(timestamp time.Time, line json.RawMessage, labels json.RawMessage) { + frame := qr.frame + frame.Fields[0].Append(timestamp) + frame.Fields[1].Append(line) + frame.Fields[2].Append(labels) +} + +func (qr QueryResultBuilder) AddRow(timestamp time.Time, line LokiEntry, labels json.RawMessage) error { + lineBytes, err := json.Marshal(line) + if err != nil { + return err + } + qr.AddRowRaw(timestamp, lineBytes, labels) + return nil +} + +// ToFrame converts the QueryResultBuilder back to a data.Frame. +func (qr QueryResultBuilder) ToFrame() *data.Frame { + return qr.frame +} + +func (qr QueryResultBuilder) AddWarn(s string) { + m := qr.frame.Meta + if m == nil { + m = &data.FrameMeta{} + qr.frame.SetMeta(m) + } + m.Notices = append(m.Notices, data.Notice{ + Severity: data.NoticeSeverityWarning, + Text: s, + Link: "", + Inspect: 0, + }) +} + const ( StateHistoryLabelKey = "from" StateHistoryLabelValue = "state-history" @@ -191,20 +254,7 @@ func (h RemoteLokiBackend) merge(res []lokiclient.Stream, folderUIDToFilter []st totalLen += len(arr.Values) } - // Create a new slice to store the merged elements. - frame := data.NewFrame("states") - - // We merge all series into a single linear history. - lbls := data.Labels(map[string]string{}) - - // We represent state history as a single merged history, that roughly corresponds to what you get in the Grafana Explore tab when querying Loki directly. - // The format is composed of the following vectors: - // 1. `time` - timestamp - when the transition happened - // 2. `line` - JSON - the full data of the transition - // 3. `labels` - JSON - the labels associated with that state transition - times := make([]time.Time, 0, totalLen) - lines := make([]json.RawMessage, 0, totalLen) - labels := make([]json.RawMessage, 0, totalLen) + queryResult := NewQueryResultBuilder(totalLen) // Initialize a slice of pointers to the current position in each array. pointers := make([]int, len(res)) @@ -259,17 +309,10 @@ func (h RemoteLokiBackend) merge(res []lokiclient.Stream, folderUIDToFilter []st pointers[minElStreamIdx]++ continue } - times = append(times, time.Unix(0, tsNano)) - labels = append(labels, lblsJson) - lines = append(lines, json.RawMessage(entryBytes)) + queryResult.AddRowRaw(time.Unix(0, tsNano), entryBytes, lblsJson) pointers[minElStreamIdx]++ } - - frame.Fields = append(frame.Fields, data.NewField(dfTime, lbls, times)) - frame.Fields = append(frame.Fields, data.NewField(dfLine, lbls, lines)) - frame.Fields = append(frame.Fields, data.NewField(dfLabels, lbls, labels)) - - return frame, nil + return queryResult.ToFrame(), nil } func StatesToStream(rule history_model.RuleMeta, states []state.StateTransition, externalLabels map[string]string, logger log.Logger) lokiclient.Stream { @@ -282,28 +325,11 @@ func StatesToStream(rule history_model.RuleMeta, states []state.StateTransition, samples := make([]lokiclient.Sample, 0, len(states)) for _, state := range states { - if !shouldRecord(state) { + if !ShouldRecord(state) { continue } - sanitizedLabels := removePrivateLabels(state.Labels) - entry := LokiEntry{ - SchemaVersion: 1, - Previous: state.PreviousFormatted(), - Current: state.Formatted(), - Values: valuesAsDataBlob(state.State), - Condition: rule.Condition, - DashboardUID: rule.DashboardUID, - PanelID: rule.PanelID, - Fingerprint: labelFingerprint(sanitizedLabels), - RuleTitle: rule.Title, - RuleID: rule.ID, - RuleUID: rule.UID, - InstanceLabels: sanitizedLabels, - } - if state.State.State == eval.Error { - entry.Error = state.Error.Error() - } + entry := StateTransitionToLokiEntry(rule, state) jsn, err := json.Marshal(entry) if err != nil { @@ -324,6 +350,28 @@ func StatesToStream(rule history_model.RuleMeta, states []state.StateTransition, } } +func StateTransitionToLokiEntry(rule history_model.RuleMeta, state state.StateTransition) LokiEntry { + sanitizedLabels := removePrivateLabels(state.Labels) + entry := LokiEntry{ + SchemaVersion: 1, + Previous: state.PreviousFormatted(), + Current: state.Formatted(), + Values: valuesAsDataBlob(state.State), + Condition: rule.Condition, + DashboardUID: rule.DashboardUID, + PanelID: rule.PanelID, + Fingerprint: labelFingerprint(sanitizedLabels), + RuleTitle: rule.Title, + RuleID: rule.ID, + RuleUID: rule.UID, + InstanceLabels: sanitizedLabels, + } + if state.State.State == eval.Error && state.Error != nil { + entry.Error = state.Error.Error() + } + return entry +} + func (h *RemoteLokiBackend) recordStreams(ctx context.Context, stream lokiclient.Stream, logger log.Logger) error { if err := h.client.Push(ctx, []lokiclient.Stream{stream}); err != nil { return err diff --git a/pkg/setting/setting_unified_alerting.go b/pkg/setting/setting_unified_alerting.go index 743f386ff52..6abaef8bc2e 100644 --- a/pkg/setting/setting_unified_alerting.go +++ b/pkg/setting/setting_unified_alerting.go @@ -156,6 +156,8 @@ type UnifiedAlertingSettings struct { // AlertmanagerMaxTemplateOutputSize specifies the maximum allowed size for rendered template output in bytes. AlertmanagerMaxTemplateOutputSize int64 + + BacktestingMaxEvaluations int } type RecordingRuleSettings struct { @@ -594,6 +596,11 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error { return fmt.Errorf("setting 'alertmanager_max_template_output_bytes' is invalid, only 0 or a positive integer are allowed") } + uaCfg.BacktestingMaxEvaluations = ua.Key("backtesting_max_evaluations").MustInt(100) + if uaCfg.BacktestingMaxEvaluations < 0 { + uaCfg.BacktestingMaxEvaluations = 100 + } + cfg.UnifiedAlerting = uaCfg return nil } diff --git a/pkg/tests/api/alerting/api_backtesting_test.go b/pkg/tests/api/alerting/api_backtesting_test.go index f07be49ff01..faf50a60da2 100644 --- a/pkg/tests/api/alerting/api_backtesting_test.go +++ b/pkg/tests/api/alerting/api_backtesting_test.go @@ -68,7 +68,7 @@ func TestBacktesting(t *testing.T) { require.Truef(t, ok, "The data file does not contain a field `data`") status, body := apiCli.SubmitRuleForBacktesting(t, request) - require.Equal(t, http.StatusOK, status) + require.Equalf(t, http.StatusOK, status, "Response: %s", body) var result data.Frame require.NoErrorf(t, json.Unmarshal([]byte(body), &result), "cannot parse response to data frame") }) @@ -107,6 +107,7 @@ func TestBacktesting(t *testing.T) { resourcepermissions.SetResourcePermissionCommand{ Actions: []string{ accesscontrol.ActionAlertingRuleRead, + accesscontrol.ActionAlertingRuleUpdate, }, Resource: "folders", ResourceID: "*", diff --git a/pkg/tests/api/alerting/test-data/api_backtesting_data.json b/pkg/tests/api/alerting/test-data/api_backtesting_data.json index d02b6905f0b..5fe0f621126 100644 --- a/pkg/tests/api/alerting/test-data/api_backtesting_data.json +++ b/pkg/tests/api/alerting/test-data/api_backtesting_data.json @@ -12,6 +12,9 @@ }, "condition": "A", "no_data_state": "Alerting", + "title": "test-rule-backtesting-data", + "rule_group": "test-group", + "namespace_uid": "test-namespace", "data": [ { "refId": "A", @@ -193,6 +196,9 @@ }, "condition": "C", "no_data_state": "Alerting", + "title": "test-rule-backtesting-data", + "rule_group": "test-group", + "namespace_uid": "test-namespace", "data": [ { "refId": "A", diff --git a/public/app/features/alerting/unified/api/backtestApi.ts b/public/app/features/alerting/unified/api/backtestApi.ts new file mode 100644 index 00000000000..14a0827cb20 --- /dev/null +++ b/public/app/features/alerting/unified/api/backtestApi.ts @@ -0,0 +1,50 @@ +import { DataFrameJSON } from '@grafana/data'; +import { AlertQuery, GrafanaAlertStateDecision, Labels } from 'app/types/unified-alerting-dto'; + +import { alertingApi } from './alertingApi'; + +/** + * Request body for the backtest API matching the BacktestConfig struct in the backend + */ +export interface BacktestRequest { + // Required time range fields + from: string; // ISO 8601 timestamp + to: string; // ISO 8601 timestamp + interval: string; // e.g., "1m", "5m" + + // Required alert definition fields + condition: string; + data: AlertQuery[]; + title: string; + no_data_state?: GrafanaAlertStateDecision; + exec_err_state?: GrafanaAlertStateDecision; + + // Optional duration fields + for?: string; + keep_firing_for?: string; + + // Optional metadata fields + labels?: Labels; + missing_series_evals_to_resolve?: number; + + // Optional rule identification fields + uid?: string; + rule_group?: string; + namespace_uid?: string; +} + +export const BACKTEST_URL = '/api/v1/rule/backtest'; + +export const backtestApi = alertingApi.injectEndpoints({ + endpoints: (build) => ({ + runBacktest: build.mutation({ + query: (requestBody) => ({ + url: BACKTEST_URL, + method: 'POST', + body: requestBody, + }), + }), + }), +}); + +export const { useRunBacktestMutation } = backtestApi; diff --git a/public/app/features/alerting/unified/components/backtesting/BacktestDropdownButton.tsx b/public/app/features/alerting/unified/components/backtesting/BacktestDropdownButton.tsx new file mode 100644 index 00000000000..7d7c4467780 --- /dev/null +++ b/public/app/features/alerting/unified/components/backtesting/BacktestDropdownButton.tsx @@ -0,0 +1,63 @@ +import { useCallback, useState } from 'react'; + +import { TimeRange, rangeUtil } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; +import { Button, Drawer, Dropdown, Menu, MenuItem } from '@grafana/ui'; + +import { RuleFormValues } from '../../types/rule-form'; + +import { BacktestPanel } from './BacktestPanel'; + +interface BacktestDropdownButtonProps { + ruleDefinition: RuleFormValues; +} + +export function BacktestDropdownButton({ ruleDefinition }: BacktestDropdownButtonProps) { + const [isBacktestPanelOpen, setIsBacktestPanelOpen] = useState(false); + const [backtestTimeRange, setBacktestTimeRange] = useState(); + + const handleTimeRangeSelect = useCallback((rawFrom: string) => { + const timeRange = rangeUtil.convertRawToRange({ from: rawFrom, to: 'now' }); + setBacktestTimeRange(timeRange); + setIsBacktestPanelOpen(true); + }, []); + + const handleCustomSelect = useCallback(() => { + setBacktestTimeRange(undefined); + setIsBacktestPanelOpen(true); + }, []); + + return ( + <> + + handleTimeRangeSelect('now-15m')} + /> + handleTimeRangeSelect('now-1h')} + /> + + + } + > + + + + {isBacktestPanelOpen && ( + setIsBacktestPanelOpen(false)} + size="md" + > + + + )} + + ); +} diff --git a/public/app/features/alerting/unified/components/backtesting/BacktestPanel.tsx b/public/app/features/alerting/unified/components/backtesting/BacktestPanel.tsx new file mode 100644 index 00000000000..1a3a4ae7706 --- /dev/null +++ b/public/app/features/alerting/unified/components/backtesting/BacktestPanel.tsx @@ -0,0 +1,200 @@ +import { css } from '@emotion/css'; +import { fromPairs, isEmpty, isEqual } from 'lodash'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { AlertLabels } from '@grafana/alerting/unstable'; +import { DataFrameJSON, GrafanaTheme2, TimeRange, rangeUtil } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; +import { + Alert, + Icon, + LoadingPlaceholder, + RefreshPicker, + Stack, + Text, + TimeRangePicker, + Tooltip, + useStyles2, +} from '@grafana/ui'; + +import { useRunBacktestMutation } from '../../api/backtestApi'; +import { RuleFormValues } from '../../types/rule-form'; +import { combineMatcherStrings } from '../../utils/alertmanager'; +import { messageFromError } from '../../utils/redux'; +import { formValuesToRulerGrafanaRuleDTO } from '../../utils/rule-form'; +import { LogRecordViewerByTimestamp } from '../rules/state-history/LogRecordViewer'; +import { LogTimelineViewer } from '../rules/state-history/LogTimelineViewer'; +import { useFrameSubset } from '../rules/state-history/LokiStateHistory'; +import { useRuleHistoryRecords } from '../rules/state-history/useRuleHistoryRecords'; + +interface BacktestPanelProps { + ruleDefinition: RuleFormValues; + initialTimeRange?: TimeRange; +} + +export function BacktestPanel({ ruleDefinition, initialTimeRange }: BacktestPanelProps) { + const styles = useStyles2(getStyles); + const [timeRange, setTimeRange] = useState( + initialTimeRange || rangeUtil.convertRawToRange({ from: 'now-15m', to: 'now' }) + ); + const [stateHistory, setStateHistory] = useState(); + const [instancesFilter, setInstancesFilter] = useState(''); + const shouldRunInitialBacktest = useRef(!!initialTimeRange); + + const [runBacktest, { isLoading, error: mutationError }] = useRunBacktestMutation(); + + const handleRunBacktest = useCallback(async () => { + // Convert form values to the proper AlertRule format + const alertRule = formValuesToRulerGrafanaRuleDTO(ruleDefinition); + + // Build requestBody matching BacktestConfig struct + const requestBody = { + // Required time range fields + from: timeRange.from.toISOString(), + to: timeRange.to.toISOString(), + interval: ruleDefinition.evaluateEvery, + + // Required alert definition fields + condition: alertRule.grafana_alert.condition, + data: alertRule.grafana_alert.data, + title: alertRule.grafana_alert.title, + no_data_state: alertRule.grafana_alert.no_data_state, + exec_err_state: alertRule.grafana_alert.exec_err_state, + + // Optional duration fields + for: alertRule.for, + keep_firing_for: alertRule.keep_firing_for, + + // Optional metadata fields + labels: alertRule.labels, + missing_series_evals_to_resolve: alertRule.grafana_alert.missing_series_evals_to_resolve, + + // Optional rule identification fields + uid: alertRule.grafana_alert.uid, + rule_group: ruleDefinition.group, + namespace_uid: ruleDefinition.folder?.uid, + }; + + try { + const result = await runBacktest(requestBody).unwrap(); + setStateHistory(result); + } catch (err) { + // Error is handled by RTK Query and available via mutationError + } + }, [ruleDefinition, timeRange, runBacktest]); + + // Update time range when initialTimeRange prop changes + useEffect(() => { + if (initialTimeRange) { + setTimeRange(initialTimeRange); + } + }, [initialTimeRange]); + + // Run backtest once after initial mount when timeRange is synchronized with initialTimeRange + useEffect(() => { + if (shouldRunInitialBacktest.current && initialTimeRange && isEqual(timeRange, initialTimeRange)) { + shouldRunInitialBacktest.current = false; + handleRunBacktest(); + } + }, [initialTimeRange, timeRange, handleRunBacktest]); + + const { dataFrames, historyRecords, commonLabels } = useRuleHistoryRecords(stateHistory, instancesFilter); + + const { frameSubset, frameTimeRange } = useFrameSubset(dataFrames); + + const onLogRecordLabelClick = useCallback( + (label: string) => { + const matcherString = combineMatcherStrings(instancesFilter, label); + setInstancesFilter(matcherString); + }, + [instancesFilter] + ); + + const hasResults = stateHistory !== undefined; + + const notices = stateHistory?.schema?.meta?.notices || []; + const errorMessage = mutationError ? messageFromError(mutationError) : null; + + return ( +
+ + {}} + onMoveBackward={() => {}} + onMoveForward={() => {}} + onZoom={() => {}} + /> + {}} + isLoading={isLoading} + noIntervalPicker={true} + /> + +
+ {isLoading && } + + {errorMessage && ( + {errorMessage} + )} + + {!isLoading && !mutationError && hasResults && notices.length > 0 && ( + + {notices.map((notice, index) => ( + + {notice.text} + + ))} + + )} + + {!isLoading && !mutationError && hasResults && ( +
+ {!isEmpty(commonLabels) && ( + + + + Common labels + + + + + + + + )} + + +
+ )} +
+
+ ); +} +const getStyles = (theme: GrafanaTheme2) => ({ + scrollableContent: css({ + flex: 1, + display: 'flex', + flexDirection: 'column', + paddingTop: theme.spacing(2), + overflow: 'hidden', + }), + resultsContainer: css({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), + flex: 1, + overflow: 'hidden', + }), +}); diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx index 85e72cca2d8..ed05cd8823c 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx @@ -60,6 +60,7 @@ import { formValuesToRulerRuleDTO, } from '../../../utils/rule-form'; import { fromRulerRule, fromRulerRuleAndRuleGroupIdentifier } from '../../../utils/rule-id'; +import { BacktestDropdownButton } from '../../backtesting/BacktestDropdownButton'; import { GrafanaRuleExporter } from '../../export/GrafanaRuleExporter'; import { AlertRuleNameAndMetric } from '../AlertRuleNameInput'; import AnnotationsStep from '../AnnotationsStep'; @@ -290,6 +291,8 @@ export const AlertRuleForm = ({ existing, prefill, isManualRestore }: Props) => Edit YAML )} + + {config.featureToggles.alertingBacktesting && }
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 85563c43905..a51e48d0e7f 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -723,6 +723,11 @@ "placeholder-value-input": "Enter a {{key}}...", "placeholder-value-input-default": "Enter custom annotation content..." }, + "backtest": { + "error-title": "Failed to run backtest", + "loading": "Running backtest...", + "panel-title": "Rule Retroactive Testing" + }, "bulk-actions": { "delete": { "success": "Rules successfully deleted from folder" @@ -2203,11 +2208,15 @@ "min-interval": "Min. Interval = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "Custom", "disableAdvancedOptions": { "text": "The selected queries and expressions cannot be converted to default. If you deactivate advanced options, your query and condition will be reset to default settings." }, + "last15m": "Last 15 minutes", + "last1h": "Last 1 hour", "preview": "Preview", - "previewCondition": "Preview alert rule condition" + "previewCondition": "Preview alert rule condition", + "testRule": "Test Rule" }, "receiver-filter": { "aria-label-contact-points": "Filter by contact points", From a345f78ae0faee08f0e51e9c04b46082ac3caec6 Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Sun, 28 Dec 2025 00:34:24 +0000 Subject: [PATCH 294/297] I18n: Download translations from Crowdin (#115717) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/cs-CZ/grafana.json | 11 ++++++++++- public/locales/de-DE/grafana.json | 11 ++++++++++- public/locales/es-ES/grafana.json | 11 ++++++++++- public/locales/fr-FR/grafana.json | 11 ++++++++++- public/locales/hu-HU/grafana.json | 11 ++++++++++- public/locales/id-ID/grafana.json | 11 ++++++++++- public/locales/it-IT/grafana.json | 11 ++++++++++- public/locales/ja-JP/grafana.json | 11 ++++++++++- public/locales/ko-KR/grafana.json | 11 ++++++++++- public/locales/nl-NL/grafana.json | 11 ++++++++++- public/locales/pl-PL/grafana.json | 11 ++++++++++- public/locales/pt-BR/grafana.json | 11 ++++++++++- public/locales/pt-PT/grafana.json | 11 ++++++++++- public/locales/ru-RU/grafana.json | 11 ++++++++++- public/locales/sv-SE/grafana.json | 11 ++++++++++- public/locales/tr-TR/grafana.json | 11 ++++++++++- public/locales/zh-Hans/grafana.json | 11 ++++++++++- public/locales/zh-Hant/grafana.json | 11 ++++++++++- 18 files changed, 180 insertions(+), 18 deletions(-) diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 2fb06738b36..7ed8c4bf808 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -729,6 +729,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "Zadejte obsah vlastní vysvětlivky…" }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "Pravidla byla úspěšně odstraněna ze složky" @@ -2219,11 +2224,15 @@ "min-interval": "Min. Interval = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "Vybrané dotazy a výrazy nelze převést na výchozí. Pokud deaktivujete pokročilé možnosti, váš dotaz a podmínka budou obnoveny do výchozího nastavení." }, + "last15m": "", + "last1h": "", "preview": "Náhled", - "previewCondition": "Podmínka pravidla náhledu výstrahy" + "previewCondition": "Podmínka pravidla náhledu výstrahy", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "Filtrovat podle kontaktních bodů", diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index 6c09564ae2f..a8d01ea13bb 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -723,6 +723,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "Inhalt der benutzerdefinierten Anmerkung eingeben …" }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "Die Regeln wurden erfolgreich aus dem Ordner gelöscht" @@ -2203,11 +2208,15 @@ "min-interval": "Mind. Intervall = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "Die ausgewählten Abfragen und Ausdrücke können nicht in die Standardeinstellung konvertiert werden. Wenn Sie die erweiterten Optionen deaktivieren, werden Ihre Abfrage und Bedingung auf die Standardeinstellungen zurückgesetzt." }, + "last15m": "", + "last1h": "", "preview": "Vorschau", - "previewCondition": "Vorschau der Warnregelbedingung" + "previewCondition": "Vorschau der Warnregelbedingung", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "Nach Kontaktpunkten filtern", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 45d955ba66c..a6f418cc1be 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -723,6 +723,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "Introduce el contenido de la anotación personalizada..." }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "Reglas eliminadas correctamente de la carpeta" @@ -2203,11 +2208,15 @@ "min-interval": "Tamaño min. Intervalo = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "Las consultas y expresiones seleccionadas no se pueden convertir a predeterminadas. Si desactivas las opciones avanzadas, tu consulta y condición se restablecerán a la configuración predeterminada." }, + "last15m": "", + "last1h": "", "preview": "Vista previa", - "previewCondition": "Vista previa de la condición de la regla de alerta" + "previewCondition": "Vista previa de la condición de la regla de alerta", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "Filtrar por puntos de contacto", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 33bf748fbc0..b6e586e10db 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -723,6 +723,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "Saisir le contenu de l’annotation personnalisée..." }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "Règles supprimées du dossier" @@ -2203,11 +2208,15 @@ "min-interval": "Min. Intervalle = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "Les requêtes et expressions sélectionnées ne peuvent pas être converties en valeurs par défaut. Si vous désactivez les options avancées, votre requête et votre condition seront réinitialisées aux valeurs par défaut." }, + "last15m": "", + "last1h": "", "preview": "Aperçu", - "previewCondition": "Aperçu de la condition de la règle d'alerte" + "previewCondition": "Aperçu de la condition de la règle d'alerte", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "Filtrer par points de contact", diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index 3704f01de9a..d51785fdb8a 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -723,6 +723,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "Adja meg az egyéni jegyzet tartalmát…" }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "A szabályok sikeresen törlődtek a mappából" @@ -2203,11 +2208,15 @@ "min-interval": "Min. intervallum = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "A kijelölt lekérdezések és kifejezések nem konvertálhatók alapértelmezettre. Ha kikapcsolja a speciális beállításokat, a lekérdezés és a feltétel visszaáll az alapértelmezett beállításokra." }, + "last15m": "", + "last1h": "", "preview": "Előnézet", - "previewCondition": "Riasztási szabály előnézeti feltétele" + "previewCondition": "Riasztási szabály előnézeti feltétele", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "Szűrés kapcsolattartási pontok szerint", diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 602acd8813a..c000333c5c2 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -720,6 +720,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "Masukkan konten anotasi kustom..." }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "Aturan berhasil dihapus dari folder" @@ -2195,11 +2200,15 @@ "min-interval": "Min. Interval = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "Kueri dan ekspresi yang dipilih tidak dapat dikonversi ke default. Jika Anda menonaktifkan opsi lanjutan, kueri dan kondisi Anda akan diatur ulang ke pengaturan default." }, + "last15m": "", + "last1h": "", "preview": "Pratinjau", - "previewCondition": "Pratinjau kondisi aturan peringatan" + "previewCondition": "Pratinjau kondisi aturan peringatan", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "Filter berdasarkan titik kontak", diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 4832c6c744c..5b5839ddf39 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -723,6 +723,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "Inserisci il contenuto dell'annotazione personalizzata..." }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "Regole eliminate dalla cartella" @@ -2203,11 +2208,15 @@ "min-interval": "Min Intervallo = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "Le query e le espressioni selezionate non possono essere convertite in predefinite. Se disattivi le opzioni avanzate, la query e la condizione verranno ripristinate alle impostazioni predefinite." }, + "last15m": "", + "last1h": "", "preview": "Anteprima", - "previewCondition": "Anteprima condizione regola di avviso" + "previewCondition": "Anteprima condizione regola di avviso", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "Filtra per punti di contatto", diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index c87617b2161..85597e5cff8 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -720,6 +720,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "カスタム注釈内容を入力..." }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "ルールがフォルダから正常に削除されました" @@ -2195,11 +2200,15 @@ "min-interval": "最小間隔= {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "選択したクエリと式はデフォルトに変換できません。高度なオプションを無効にすると、クエリと条件はデフォルト設定にリセットされます。" }, + "last15m": "", + "last1h": "", "preview": "プレビュー", - "previewCondition": "アラートルール条件をプレビューする" + "previewCondition": "アラートルール条件をプレビューする", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "コンタクトポイントで絞り込む", diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index 65d967807ea..25e6bea87a4 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -720,6 +720,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "사용자 지정 주석 내용을 입력하세요..." }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "폴더에서 규칙이 성공적으로 삭제되었습니다" @@ -2195,11 +2200,15 @@ "min-interval": "최소 간격 = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "선택한 쿼리와 표현식을 기본값으로 변환할 수 없습니다. 고급 옵션을 비활성화하면 쿼리와 조건이 기본 설정으로 재설정됩니다." }, + "last15m": "", + "last1h": "", "preview": "미리보기", - "previewCondition": "경고 규칙 조건 미리보기" + "previewCondition": "경고 규칙 조건 미리보기", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "연락처로 필터링", diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index a1d9ba17c5b..b1f700e5957 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -723,6 +723,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "Aangepaste annotatie-inhoud invoeren..." }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "Regels zijn verwijderd uit de map" @@ -2203,11 +2208,15 @@ "min-interval": "Min. Interval = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "De geselecteerde query's en expressies kunnen niet worden geconverteerd naar standaard. Als je geavanceerde opties deactiveert, worden je query en voorwaarde teruggezet naar de standaardinstellingen." }, + "last15m": "", + "last1h": "", "preview": "Voorbeeld", - "previewCondition": "Voorbeeld waarschuwingsregel voorwaarde" + "previewCondition": "Voorbeeld waarschuwingsregel voorwaarde", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "Filteren op contactpunten", diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index c7d04e0cd8b..2705b11c9ae 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -729,6 +729,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "Wpisz treść niestandardowej adnotacji…" }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "Reguły zostały usunięte z folderu" @@ -2219,11 +2224,15 @@ "min-interval": "Min. odstęp czasu = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "Nie można przekonwertować wybranych zapytań i wyrażeń na domyślne. Jeśli wyłączysz opcje zaawansowane, zapytanie i warunek zostaną zresetowane do ustawień domyślnych." }, + "last15m": "", + "last1h": "", "preview": "Podgląd", - "previewCondition": "Podgląd warunku reguły alertu" + "previewCondition": "Podgląd warunku reguły alertu", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "Filtruj według punktów kontaktu", diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index eee46fc8344..250376a959f 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -723,6 +723,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "Insira o conteúdo da anotação personalizada…" }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "As regras foram excluídas da pasta" @@ -2203,11 +2208,15 @@ "min-interval": "Mín. Intervalo = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "As consultas e expressões selecionadas não podem ser convertidas para o padrão. Se você desativar as opções avançadas, sua consulta e condição serão redefinidas para as configurações padrão." }, + "last15m": "", + "last1h": "", "preview": "Visualizar", - "previewCondition": "Visualizar condição de regra de alerta" + "previewCondition": "Visualizar condição de regra de alerta", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "Filtrar por pontos de contato", diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index 415075e65ab..beb5f7d3de8 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -723,6 +723,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "Introduzir o conteúdo da anotação personalizada..." }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "Regras eliminadas da pasta com sucesso" @@ -2203,11 +2208,15 @@ "min-interval": "Min. Intervalo = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "As consultas e expressões selecionadas não podem ser convertidas para padrão. Se desativar as opções avançadas, a sua consulta e condição serão repostas para as definições padrão." }, + "last15m": "", + "last1h": "", "preview": "Pré-visualizar", - "previewCondition": "Pré-visualizar a condição da regra de alerta" + "previewCondition": "Pré-visualizar a condição da regra de alerta", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "Filtrar por pontos de contacto", diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index a8aab23f3d0..8655e70fba0 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -729,6 +729,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "Ввести содержимое пользовательской аннотации..." }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "Правила удалены из папки" @@ -2219,11 +2224,15 @@ "min-interval": "Мин. интервал = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "Выбранные запросы и выражения не могут быть преобразованы в используемые по умолчанию. Если вы отключите расширенные параметры, ваш запрос и условие будут сброшены до настроек по умолчанию." }, + "last15m": "", + "last1h": "", "preview": "Предварительный просмотр", - "previewCondition": "Предварительный просмотр условия правила оповещения" + "previewCondition": "Предварительный просмотр условия правила оповещения", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "Фильтр по точкам контакта", diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index f3c4effc8b3..4869152e5e8 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -723,6 +723,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "Ange innehåll för anpassad kommentar …" }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "Reglerna har raderats från mappen" @@ -2203,11 +2208,15 @@ "min-interval": "Min. Intervall = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "De valda frågorna och uttrycken kan inte konverteras till standard. Om du inaktiverar avancerade alternativ kommer din fråga och ditt villkor att återställas till standardinställningarna." }, + "last15m": "", + "last1h": "", "preview": "Förhandsgranska", - "previewCondition": "Förhandsgranska varningsregeltillstånd" + "previewCondition": "Förhandsgranska varningsregeltillstånd", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "Filtrera efter kontaktpunkter", diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 7cd8b7b5939..ad54e0fd3e8 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -723,6 +723,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "Özel ek açıklama içeriği girin..." }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "Kurallar klasörden başarıyla silindi" @@ -2203,11 +2208,15 @@ "min-interval": "Min. Aralık = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "Seçilen sorgular ve ifadeler varsayılana dönüştürülemez. Gelişmiş seçenekleri devre dışı bırakırsanız sorgunuz ve koşulunuz varsayılan ayarlara sıfırlanır." }, + "last15m": "", + "last1h": "", "preview": "Ön izleme", - "previewCondition": "Uyarı kuralı koşulunu ön izle" + "previewCondition": "Uyarı kuralı koşulunu ön izle", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index b36e525f676..7a4b67ba8e7 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -720,6 +720,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "输入自定义注释内容..." }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "规则已成功从文件夹中删除" @@ -2195,11 +2200,15 @@ "min-interval": "最小间隔 = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "无法将所选查询和表达式转换为默认值。如果停用高级选项,您的查询和条件将重置为默认设置。" }, + "last15m": "", + "last1h": "", "preview": "预览", - "previewCondition": "预览提醒规则条件" + "previewCondition": "预览提醒规则条件", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "按联络点筛选", diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index 0302a7ffb6f..5e3786ac559 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -720,6 +720,11 @@ "placeholder-value-input": "", "placeholder-value-input-default": "輸入自訂註解內容…" }, + "backtest": { + "error-title": "", + "loading": "", + "panel-title": "" + }, "bulk-actions": { "delete": { "success": "已成功從資料夾中刪除規則" @@ -2195,11 +2200,15 @@ "min-interval": "最小間隔 = {{minInterval}}" }, "queryAndExpressionsStep": { + "custom": "", "disableAdvancedOptions": { "text": "所選查詢和表達式無法轉換為預設值。如果停用進階選項,您的查詢和條件將重設為預設設定。" }, + "last15m": "", + "last1h": "", "preview": "預覽", - "previewCondition": "預覽警報規則條件" + "previewCondition": "預覽警報規則條件", + "testRule": "" }, "receiver-filter": { "aria-label-contact-points": "按聯絡點篩選", From 4ba2fe6cce816da9c98d26ba473fda48261f897a Mon Sep 17 00:00:00 2001 From: Matheus Macabu Date: Mon, 29 Dec 2025 09:31:58 +0100 Subject: [PATCH 295/297] Auditing: Add Event struct to map audit logs into (#115509) --- pkg/apiserver/auditing/event.go | 88 ++++++++++++++++++++++++++++ pkg/apiserver/auditing/event_test.go | 64 ++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 pkg/apiserver/auditing/event.go create mode 100644 pkg/apiserver/auditing/event_test.go diff --git a/pkg/apiserver/auditing/event.go b/pkg/apiserver/auditing/event.go new file mode 100644 index 00000000000..dc5829096e6 --- /dev/null +++ b/pkg/apiserver/auditing/event.go @@ -0,0 +1,88 @@ +package auditing + +import ( + "encoding/json" + "time" +) + +type Event struct { + // The namespace the action was performed in. + Namespace string `json:"namespace"` + + // When it happened. + ObservedAt time.Time `json:"-"` // see MarshalJSON for why this is omitted + + // Who/what performed the action. + SubjectName string `json:"subjectName"` + SubjectUID string `json:"subjectUID"` + + // What was performed. + Verb string `json:"verb"` + + // The object the action was performed on. For verbs like "list" this will be empty. + Object string `json:"object,omitempty"` + + // API information. + APIGroup string `json:"apiGroup,omitempty"` + APIVersion string `json:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty"` + + // Outcome of the action. + Outcome EventOutcome `json:"outcome"` + + // Extra fields to add more context to the event. + Extra map[string]string `json:"extra,omitempty"` +} + +func (e Event) Time() time.Time { + return e.ObservedAt +} + +func (e Event) MarshalJSON() ([]byte, error) { + type Alias Event + return json.Marshal(&struct { + FormattedTimestamp string `json:"observedAt"` + Alias + }{ + FormattedTimestamp: e.ObservedAt.UTC().Format(time.RFC3339Nano), + Alias: (Alias)(e), + }) +} + +func (e Event) KVPairs() []any { + args := []any{ + "audit", true, + "namespace", e.Namespace, + "observedAt", e.ObservedAt.UTC().Format(time.RFC3339Nano), + "subjectName", e.SubjectName, + "subjectUID", e.SubjectUID, + "verb", e.Verb, + "object", e.Object, + "apiGroup", e.APIGroup, + "apiVersion", e.APIVersion, + "kind", e.Kind, + "outcome", e.Outcome, + } + + if len(e.Extra) > 0 { + extraArgs := make([]any, 0, len(e.Extra)*2) + + for k, v := range e.Extra { + extraArgs = append(extraArgs, "extra_"+k, v) + } + + args = append(args, extraArgs...) + } + + return args +} + +type EventOutcome string + +const ( + EventOutcomeUnknown EventOutcome = "unknown" + EventOutcomeSuccess EventOutcome = "success" + EventOutcomeFailureUnauthorized EventOutcome = "failure_unauthorized" + EventOutcomeFailureNotFound EventOutcome = "failure_not_found" + EventOutcomeFailureGeneric EventOutcome = "failure_generic" +) diff --git a/pkg/apiserver/auditing/event_test.go b/pkg/apiserver/auditing/event_test.go new file mode 100644 index 00000000000..3267936b02a --- /dev/null +++ b/pkg/apiserver/auditing/event_test.go @@ -0,0 +1,64 @@ +package auditing_test + +import ( + "encoding/json" + "strconv" + "strings" + "testing" + "time" + + "github.com/grafana/grafana/pkg/apiserver/auditing" + "github.com/stretchr/testify/require" +) + +func TestEvent_MarshalJSON(t *testing.T) { + t.Parallel() + + t.Run("marshals the event", func(t *testing.T) { + t.Parallel() + + now := time.Now() + + event := auditing.Event{ + ObservedAt: now, + Extra: map[string]string{"k1": "v1", "k2": "v2"}, + } + + data, err := json.Marshal(event) + require.NoError(t, err) + + var result map[string]any + require.NoError(t, json.Unmarshal(data, &result)) + + require.Equal(t, event.Time().UTC().Format(time.RFC3339Nano), result["observedAt"]) + require.NotNil(t, result["extra"]) + require.Len(t, result["extra"], 2) + }) +} + +func TestEvent_KVPairs(t *testing.T) { + t.Parallel() + + t.Run("records extra fields", func(t *testing.T) { + t.Parallel() + + extraFields := 2 + extra := make(map[string]string, 0) + for i := 0; i < extraFields; i++ { + extra[strconv.Itoa(i)] = "value" + } + + event := auditing.Event{Extra: extra} + + kvPairs := event.KVPairs() + + extraCount := 0 + for i := 0; i < len(kvPairs); i += 2 { + if strings.HasPrefix(kvPairs[i].(string), "extra_") { + extraCount++ + } + } + + require.Equal(t, extraCount, extraFields) + }) +} From 0b58cd3900f721224f616b572aadb424654c6eca Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Mon, 29 Dec 2025 09:53:45 +0100 Subject: [PATCH 296/297] Dashboard: Remove BOMs from links during conversion (#115689) * Dashboard: Add test case for BOM characters in link URLs This test demonstrates the issue where BOM (Byte Order Mark) characters in dashboard link URLs cause CUE validation errors during v1 to v2 conversion ('illegal byte order mark'). The test input contains BOMs in various URL locations: - Dashboard links - Panel data links - Field config override links - Options dataLinks - Field config default links * Dashboard: Strip BOM characters from URLs during v1 to v2 conversion BOM (Byte Order Mark) characters in dashboard link URLs cause CUE validation errors ('illegal byte order mark') when opening v2 dashboards. This fix strips BOMs from all URL fields during conversion: - Dashboard links - Panel data links - Field config override links - Options dataLinks - Field config default links The stripBOM helper recursively processes nested structures to ensure all string values have BOMs removed. * Dashboard: Strip BOM characters in frontend v2 conversion Add stripBOMs parameter to sortedDeepCloneWithoutNulls utility to remove Byte Order Mark (U+FEFF) characters from all strings when serializing dashboards to v2 format. This prevents CUE validation errors ('illegal byte order mark') that occur when BOMs are present in any string field. BOMs can be introduced through copy/paste from certain editors or text sources. Applied at the final serialization step so it catches BOMs from: - Existing v1 dashboards being converted - New data entered during dashboard editing --- .../testdata/input/v1beta1.bom-in-links.json | 142 ++++++++++ ...estdata-nested-variables.v42.v2alpha1.json | 2 +- ...testdata-nested-variables.v42.v2beta1.json | 2 +- .../v0alpha1.gauge_tests_new.v42.v1beta1.json | 2 +- ...v0alpha1.gauge_tests_new.v42.v2alpha1.json | 2 +- .../v0alpha1.gauge_tests_new.v42.v2beta1.json | 2 +- ...a1.gauge_tests_old_to_new.v42.v1beta1.json | 2 +- ...1.gauge_tests_old_to_new.v42.v2alpha1.json | 2 +- ...a1.gauge_tests_old_to_new.v42.v2beta1.json | 2 +- .../output/v1beta1.bom-in-links.v0alpha1.json | 161 ++++++++++++ .../output/v1beta1.bom-in-links.v2alpha1.json | 242 +++++++++++++++++ .../output/v1beta1.bom-in-links.v2beta1.json | 246 ++++++++++++++++++ .../conversion/v1beta1_to_v2alpha1.go | 54 +++- public/app/core/utils/object.ts | 16 +- .../transformSceneToSaveModelSchemaV2.ts | 3 +- 15 files changed, 861 insertions(+), 19 deletions(-) create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.bom-in-links.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v0alpha1.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v2alpha1.json create mode 100644 apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v2beta1.json diff --git a/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.bom-in-links.json b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.bom-in-links.json new file mode 100644 index 00000000000..86992c3380c --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/input/v1beta1.bom-in-links.json @@ -0,0 +1,142 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "bom-in-links-test", + "namespace": "org-1", + "labels": { + "test": "bom-stripping" + } + }, + "spec": { + "title": "BOM Stripping Test Dashboard", + "description": "Testing that BOM characters are stripped from URLs during conversion", + "schemaVersion": 42, + "tags": ["test", "bom"], + "editable": true, + "links": [ + { + "title": "Dashboard link with BOM", + "type": "link", + "url": "http://example.com?var=${datasource}&other=value", + "targetBlank": true, + "icon": "external link" + } + ], + "panels": [ + { + "id": 1, + "type": "table", + "title": "Panel with BOM in field config override links", + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + {"color": "green"}, + {"color": "red", "value": 80} + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "server" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "title": "Override link with BOM", + "url": "http://localhost:3000/d/test?var-datacenter=${__data.fields[datacenter]}&var-server=${__value.raw}" + } + ] + } + ] + } + ] + }, + "links": [ + { + "title": "Panel data link with BOM", + "url": "http://example.com/${__data.fields.cluster}&var=value", + "targetBlank": true + } + ], + "targets": [ + { + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "test-ds" + } + } + ] + }, + { + "id": 2, + "type": "timeseries", + "title": "Panel with BOM in options dataLinks", + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "options": { + "legend": { + "showLegend": true, + "displayMode": "list", + "placement": "bottom" + }, + "dataLinks": [ + { + "title": "Options data link with BOM", + "url": "http://example.com?series=${__series.name}&time=${__value.time}", + "targetBlank": true + } + ] + }, + "fieldConfig": { + "defaults": { + "links": [ + { + "title": "Field config default link with BOM", + "url": "http://example.com?field=${__field.name}&value=${__value.raw}", + "targetBlank": false + } + ] + }, + "overrides": [] + }, + "targets": [ + { + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "test-ds" + } + } + ] + } + ], + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m"] + } + } +} + diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json index 89857905689..b1dbd3de041 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2alpha1.json @@ -120,7 +120,7 @@ "value": [ { "title": "filter", - "url": "http://localhost:3000/d/-Y-tnEDWk/templating-nested-template-variables?var-datacenter=${__data.fields[datacenter]}\u0026var-server=${__value.raw}" + "url": "http://localhost:3000/d/-Y-tnEDWk/templating-nested-template-variables?var-datacenter=${__data.fields[datacenter]}\u0026var-server=${__value.raw}" } ] } diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json index 13320b47904..9089dd1d1fb 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/feature-templating/v0alpha1.testdata-nested-variables.v42.v2beta1.json @@ -124,7 +124,7 @@ "value": [ { "title": "filter", - "url": "http://localhost:3000/d/-Y-tnEDWk/templating-nested-template-variables?var-datacenter=${__data.fields[datacenter]}\u0026var-server=${__value.raw}" + "url": "http://localhost:3000/d/-Y-tnEDWk/templating-nested-template-variables?var-datacenter=${__data.fields[datacenter]}\u0026var-server=${__value.raw}" } ] } diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v1beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v1beta1.json index e04d448a5b8..66ce1cd0f3a 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v1beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v1beta1.json @@ -2051,4 +2051,4 @@ "storedVersion": "v0alpha1" } } -} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2alpha1.json index 0e6e3e13da5..95850646c59 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2alpha1.json @@ -2691,4 +2691,4 @@ "storedVersion": "v0alpha1" } } -} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2beta1.json index ad2b8ca0385..fda0d31e71b 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_new.v42.v2beta1.json @@ -2764,4 +2764,4 @@ "storedVersion": "v0alpha1" } } -} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v1beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v1beta1.json index 1d9f7e56513..2dddd657c5f 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v1beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v1beta1.json @@ -1173,4 +1173,4 @@ "storedVersion": "v0alpha1" } } -} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2alpha1.json index 7b3f601b5cf..db19ac588c1 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2alpha1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2alpha1.json @@ -1618,4 +1618,4 @@ "storedVersion": "v0alpha1" } } -} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2beta1.json index 534e7a1600c..8ddc6feb297 100644 --- a/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2beta1.json +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/migrated_dev_dashboards/panel-gauge/v0alpha1.gauge_tests_old_to_new.v42.v2beta1.json @@ -1670,4 +1670,4 @@ "storedVersion": "v0alpha1" } } -} +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v0alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v0alpha1.json new file mode 100644 index 00000000000..449e76f1173 --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v0alpha1.json @@ -0,0 +1,161 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "bom-in-links-test", + "namespace": "org-1", + "labels": { + "test": "bom-stripping" + } + }, + "spec": { + "description": "Testing that BOM characters are stripped from URLs during conversion", + "editable": true, + "links": [ + { + "icon": "external link", + "targetBlank": true, + "title": "Dashboard link with BOM", + "type": "link", + "url": "http://example.com?var=${datasource}\u0026other=value" + } + ], + "panels": [ + { + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "server" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "title": "Override link with BOM", + "url": "http://localhost:3000/d/test?var-datacenter=${__data.fields[datacenter]}\u0026var-server=${__value.raw}" + } + ] + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "links": [ + { + "targetBlank": true, + "title": "Panel data link with BOM", + "url": "http://example.com/${__data.fields.cluster}\u0026var=value" + } + ], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "test-ds" + }, + "refId": "A" + } + ], + "title": "Panel with BOM in field config override links", + "type": "table" + }, + { + "fieldConfig": { + "defaults": { + "links": [ + { + "targetBlank": false, + "title": "Field config default link with BOM", + "url": "http://example.com?field=${__field.name}\u0026value=${__value.raw}" + } + ] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "dataLinks": [ + { + "targetBlank": true, + "title": "Options data link with BOM", + "url": "http://example.com?series=${__series.name}\u0026time=${__value.time}" + } + ], + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "test-ds" + }, + "refId": "A" + } + ], + "title": "Panel with BOM in options dataLinks", + "type": "timeseries" + } + ], + "schemaVersion": 42, + "tags": [ + "test", + "bom" + ], + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m" + ] + }, + "title": "BOM Stripping Test Dashboard" + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v2alpha1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v2alpha1.json new file mode 100644 index 00000000000..38547ea5b8e --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v2alpha1.json @@ -0,0 +1,242 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2alpha1", + "metadata": { + "name": "bom-in-links-test", + "namespace": "org-1", + "labels": { + "test": "bom-stripping" + } + }, + "spec": { + "annotations": [], + "cursorSync": "Off", + "description": "Testing that BOM characters are stripped from URLs during conversion", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with BOM in field config override links", + "description": "", + "links": [ + { + "title": "Panel data link with BOM", + "url": "http://example.com/${__data.fields.cluster}\u0026var=value", + "targetBlank": true + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "prometheus", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "test-ds" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "table", + "spec": { + "pluginVersion": "", + "options": {}, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "server" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "title": "Override link with BOM", + "url": "http://localhost:3000/d/test?var-datacenter=${__data.fields[datacenter]}\u0026var-server=${__value.raw}" + } + ] + } + ] + } + ] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with BOM in options dataLinks", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "prometheus", + "spec": {} + }, + "datasource": { + "type": "prometheus", + "uid": "test-ds" + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "timeseries", + "spec": { + "pluginVersion": "", + "options": { + "dataLinks": [ + { + "targetBlank": true, + "title": "Options data link with BOM", + "url": "http://example.com?series=${__series.name}\u0026time=${__value.time}" + } + ], + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + } + }, + "fieldConfig": { + "defaults": { + "links": [ + { + "targetBlank": false, + "title": "Field config default link with BOM", + "url": "http://example.com?field=${__field.name}\u0026value=${__value.raw}" + } + ] + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [ + { + "title": "Dashboard link with BOM", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": "http://example.com?var=${datasource}\u0026other=value", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + } + ], + "liveNow": false, + "preload": false, + "tags": [ + "test", + "bom" + ], + "timeSettings": { + "timezone": "browser", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "BOM Stripping Test Dashboard", + "variables": [] + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v2beta1.json b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v2beta1.json new file mode 100644 index 00000000000..d85da89fe7a --- /dev/null +++ b/apps/dashboard/pkg/migration/conversion/testdata/output/v1beta1.bom-in-links.v2beta1.json @@ -0,0 +1,246 @@ +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v2beta1", + "metadata": { + "name": "bom-in-links-test", + "namespace": "org-1", + "labels": { + "test": "bom-stripping" + } + }, + "spec": { + "annotations": [], + "cursorSync": "Off", + "description": "Testing that BOM characters are stripped from URLs during conversion", + "editable": true, + "elements": { + "panel-1": { + "kind": "Panel", + "spec": { + "id": 1, + "title": "Panel with BOM in field config override links", + "description": "", + "links": [ + { + "title": "Panel data link with BOM", + "url": "http://example.com/${__data.fields.cluster}\u0026var=value", + "targetBlank": true + } + ], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "test-ds" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": {}, + "fieldConfig": { + "defaults": { + "thresholds": { + "mode": "absolute", + "steps": [ + { + "value": null, + "color": "green" + }, + { + "value": 80, + "color": "red" + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "server" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "title": "Override link with BOM", + "url": "http://localhost:3000/d/test?var-datacenter=${__data.fields[datacenter]}\u0026var-server=${__value.raw}" + } + ] + } + ] + } + ] + } + } + } + } + }, + "panel-2": { + "kind": "Panel", + "spec": { + "id": 2, + "title": "Panel with BOM in options dataLinks", + "description": "", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "test-ds" + }, + "spec": {} + }, + "refId": "A", + "hidden": false + } + } + ], + "transformations": [], + "queryOptions": {} + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "timeseries", + "version": "", + "spec": { + "options": { + "dataLinks": [ + { + "targetBlank": true, + "title": "Options data link with BOM", + "url": "http://example.com?series=${__series.name}\u0026time=${__value.time}" + } + ], + "legend": { + "displayMode": "list", + "placement": "bottom", + "showLegend": true + } + }, + "fieldConfig": { + "defaults": { + "links": [ + { + "targetBlank": false, + "title": "Field config default link with BOM", + "url": "http://example.com?field=${__field.name}\u0026value=${__value.raw}" + } + ] + }, + "overrides": [] + } + } + } + } + } + }, + "layout": { + "kind": "GridLayout", + "spec": { + "items": [ + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-1" + } + } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 12, + "y": 0, + "width": 12, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-2" + } + } + } + ] + } + }, + "links": [ + { + "title": "Dashboard link with BOM", + "type": "link", + "icon": "external link", + "tooltip": "", + "url": "http://example.com?var=${datasource}\u0026other=value", + "tags": [], + "asDropdown": false, + "targetBlank": true, + "includeVars": false, + "keepTime": false + } + ], + "liveNow": false, + "preload": false, + "tags": [ + "test", + "bom" + ], + "timeSettings": { + "timezone": "browser", + "from": "now-6h", + "to": "now", + "autoRefresh": "", + "autoRefreshIntervals": [ + "5s", + "10s", + "30s", + "1m", + "5m" + ], + "hideTimepicker": false, + "fiscalYearStartMonth": 0 + }, + "title": "BOM Stripping Test Dashboard", + "variables": [] + }, + "status": { + "conversion": { + "failed": false, + "storedVersion": "v1beta1" + } + } +} \ No newline at end of file diff --git a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go index 224f222ae33..b63e0146cc2 100644 --- a/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go +++ b/apps/dashboard/pkg/migration/conversion/v1beta1_to_v2alpha1.go @@ -229,6 +229,36 @@ func getBoolField(m map[string]interface{}, key string, defaultValue bool) bool return defaultValue } +// stripBOM removes Byte Order Mark (BOM) characters from a string. +// BOMs (U+FEFF) can be introduced through copy/paste from certain editors +// and cause CUE validation errors ("illegal byte order mark"). +func stripBOM(s string) string { + return strings.ReplaceAll(s, "\ufeff", "") +} + +// stripBOMFromInterface recursively strips BOM characters from all strings +// in an interface{} value (map, slice, or string). +func stripBOMFromInterface(v interface{}) interface{} { + switch val := v.(type) { + case string: + return stripBOM(val) + case map[string]interface{}: + result := make(map[string]interface{}, len(val)) + for k, v := range val { + result[k] = stripBOMFromInterface(v) + } + return result + case []interface{}: + result := make([]interface{}, len(val)) + for i, item := range val { + result[i] = stripBOMFromInterface(item) + } + return result + default: + return v + } +} + func getUnionField[T ~string](m map[string]interface{}, key string) *T { if val, ok := m[key]; ok { if str, ok := val.(string); ok && str != "" { @@ -393,7 +423,8 @@ func transformLinks(dashboard map[string]interface{}) []dashv2alpha1.DashboardDa // Optional field - only set if present if url, exists := linkMap["url"]; exists { if urlStr, ok := url.(string); ok { - dashLink.Url = &urlStr + cleanUrl := stripBOM(urlStr) + dashLink.Url = &cleanUrl } } @@ -2239,7 +2270,7 @@ func transformDataLinks(panelMap map[string]interface{}) []dashv2alpha1.Dashboar if linkMap, ok := link.(map[string]interface{}); ok { dataLink := dashv2alpha1.DashboardDataLink{ Title: schemaversion.GetStringValue(linkMap, "title"), - Url: schemaversion.GetStringValue(linkMap, "url"), + Url: stripBOM(schemaversion.GetStringValue(linkMap, "url")), } if _, exists := linkMap["targetBlank"]; exists { targetBlank := getBoolField(linkMap, "targetBlank", false) @@ -2331,6 +2362,12 @@ func buildVizConfig(panelMap map[string]interface{}) dashv2alpha1.DashboardVizCo } } + // Strip BOMs from options (may contain dataLinks with URLs that have BOMs) + cleanedOptions := stripBOMFromInterface(options) + if cleanedMap, ok := cleanedOptions.(map[string]interface{}); ok { + options = cleanedMap + } + // Build field config by mapping each field individually fieldConfigSource := extractFieldConfigSource(fieldConfig) @@ -2474,9 +2511,14 @@ func extractFieldConfigDefaults(defaults map[string]interface{}) dashv2alpha1.Da hasDefaults = true } - // Extract array field + // Extract array field - strip BOMs from link URLs if linksArray, ok := extractArrayField(defaults, "links"); ok { - fieldConfigDefaults.Links = linksArray + cleanedLinks := stripBOMFromInterface(linksArray) + if cleanedArray, ok := cleanedLinks.([]interface{}); ok { + fieldConfigDefaults.Links = cleanedArray + } else { + fieldConfigDefaults.Links = linksArray + } hasDefaults = true } @@ -2762,9 +2804,11 @@ func extractFieldConfigOverrides(fieldConfig map[string]interface{}) []dashv2alp fieldOverride.Properties = make([]dashv2alpha1.DashboardDynamicConfigValue, 0, len(propertiesArray)) for _, property := range propertiesArray { if propertyMap, ok := property.(map[string]interface{}); ok { + // Strip BOMs from property values (may contain links with URLs) + cleanedValue := stripBOMFromInterface(propertyMap["value"]) fieldOverride.Properties = append(fieldOverride.Properties, dashv2alpha1.DashboardDynamicConfigValue{ Id: schemaversion.GetStringValue(propertyMap, "id"), - Value: propertyMap["value"], + Value: cleanedValue, }) } } diff --git a/public/app/core/utils/object.ts b/public/app/core/utils/object.ts index 7ace78598c4..ba1426b163c 100644 --- a/public/app/core/utils/object.ts +++ b/public/app/core/utils/object.ts @@ -1,23 +1,29 @@ -import { isArray, isPlainObject } from 'lodash'; +import { isArray, isPlainObject, isString } from 'lodash'; /** * @returns A deep clone of the object, but with any null value removed. * @param value - The object to be cloned and cleaned. * @param convertInfinity - If true, -Infinity or Infinity is converted to 0. * This is because Infinity is not a valid JSON value, and sometimes we want to convert it to 0 instead of default null. + * @param stripBOMs - If true, strips Byte Order Mark (BOM) characters from all strings. + * BOMs (U+FEFF) can cause CUE validation errors ("illegal byte order mark"). */ -export function sortedDeepCloneWithoutNulls(value: T, convertInfinity?: boolean): T { +export function sortedDeepCloneWithoutNulls(value: T, convertInfinity?: boolean, stripBOMs?: boolean): T { if (isArray(value)) { - return value.map((item) => sortedDeepCloneWithoutNulls(item, convertInfinity)) as unknown as T; + return value.map((item) => sortedDeepCloneWithoutNulls(item, convertInfinity, stripBOMs)) as unknown as T; } if (isPlainObject(value)) { return Object.keys(value as { [key: string]: any }) .sort() .reduce((acc: any, key) => { - const v = (value as any)[key]; + let v = (value as any)[key]; // Remove null values if (v != null) { - acc[key] = sortedDeepCloneWithoutNulls(v, convertInfinity); + // Strip BOMs from strings + if (stripBOMs && isString(v)) { + v = v.replace(/\ufeff/g, ''); + } + acc[key] = sortedDeepCloneWithoutNulls(v, convertInfinity, stripBOMs); } if (convertInfinity && (v === Infinity || v === -Infinity)) { diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index 0ef2a5e5f05..90c7f5e2e61 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -144,7 +144,8 @@ export function transformSceneToSaveModelSchemaV2(scene: DashboardScene, isSnaps try { // validateDashboardSchemaV2 will throw an error if the dashboard is not valid if (validateDashboardSchemaV2(dashboardSchemaV2)) { - return sortedDeepCloneWithoutNulls(dashboardSchemaV2, true); + // Strip BOMs from all strings to prevent CUE validation errors ("illegal byte order mark") + return sortedDeepCloneWithoutNulls(dashboardSchemaV2, true, true); } // should never reach this point, validation should throw an error throw new Error('Error we could transform the dashboard to schema v2: ' + dashboardSchemaV2); From 30ad61e0e9fe02c4846e56d0c43ca5bf66907762 Mon Sep 17 00:00:00 2001 From: Ivan Ortega Alba Date: Mon, 29 Dec 2025 10:29:50 +0100 Subject: [PATCH 297/297] Dashboards: Fix adhoc filter click when panel has no panel-level datasource (#115576) * V2: Panel datasource is defined only for mixed ds * if getDatasourceFromQueryRunner only returns ds.type, resolve to full ds ref throgh ds service --------- Co-authored-by: Haris Rozajac --- .../scene/setDashboardPanelContext.test.ts | 35 +++++++++++++++- .../scene/setDashboardPanelContext.ts | 41 ++++++++++++++++--- .../dashboard-scene/utils/drilldownUtils.ts | 4 +- .../dashboard-scene/utils/urlBuilders.ts | 5 ++- .../features/dashboard-scene/utils/utils.ts | 22 +++++++++- 5 files changed, 95 insertions(+), 12 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/setDashboardPanelContext.test.ts b/public/app/features/dashboard-scene/scene/setDashboardPanelContext.test.ts index d5669497180..cf1968f45f7 100644 --- a/public/app/features/dashboard-scene/scene/setDashboardPanelContext.test.ts +++ b/public/app/features/dashboard-scene/scene/setDashboardPanelContext.test.ts @@ -1,10 +1,10 @@ import { AdHocVariableModel, EventBusSrv, GroupByVariableModel, VariableModel } from '@grafana/data'; import { BackendSrv, config, setBackendSrv } from '@grafana/runtime'; -import { GroupByVariable, sceneGraph } from '@grafana/scenes'; +import { GroupByVariable, sceneGraph, SceneQueryRunner } from '@grafana/scenes'; import { AdHocFilterItem, PanelContext } from '@grafana/ui'; import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene'; -import { findVizPanelByKey } from '../utils/utils'; +import { findVizPanelByKey, getQueryRunnerFor } from '../utils/utils'; import { getAdHocFilterVariableFor, setDashboardPanelContext } from './setDashboardPanelContext'; @@ -159,6 +159,23 @@ describe('setDashboardPanelContext', () => { // Verify existing filter value updated expect(variable.state.filters[1].operator).toBe('!='); }); + + it('Should use existing adhoc filter when panel has no panel-level datasource because queries have all the same datasources (v2 behavior)', () => { + const { scene, context } = buildTestScene({ existingFilterVariable: true, panelDatasourceUndefined: true }); + + const variable = getAdHocFilterVariableFor(scene, { uid: 'my-ds-uid' }); + variable.setState({ filters: [] }); + + context.onAddAdHocFilter!({ key: 'hello', value: 'world', operator: '=' }); + + // Should use the existing adhoc filter variable, not create a new one + expect(variable.state.filters).toEqual([{ key: 'hello', value: 'world', operator: '=' }]); + + // Verify no new adhoc variables were created + const variables = sceneGraph.getVariables(scene); + const adhocVars = variables.state.variables.filter((v) => v.state.type === 'adhoc'); + expect(adhocVars.length).toBe(1); + }); }); describe('getFiltersBasedOnGrouping', () => { @@ -312,6 +329,7 @@ interface SceneOptions { existingFilterVariable?: boolean; existingGroupByVariable?: boolean; groupByDatasourceUid?: string; + panelDatasourceUndefined?: boolean; } function buildTestScene(options: SceneOptions) { @@ -385,6 +403,19 @@ function buildTestScene(options: SceneOptions) { }); const vizPanel = findVizPanelByKey(scene, 'panel-4')!; + + // Simulate v2 dashboard behavior where non-mixed panels don't have panel-level datasource + // but the queries have their own datasources + if (options.panelDatasourceUndefined) { + const queryRunner = getQueryRunnerFor(vizPanel); + if (queryRunner instanceof SceneQueryRunner) { + queryRunner.setState({ + datasource: undefined, + queries: [{ refId: 'A', datasource: { uid: 'my-ds-uid', type: 'prometheus' } }], + }); + } + } + const context: PanelContext = { eventBus: new EventBusSrv(), eventsScope: 'global', diff --git a/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts b/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts index c9b3fdf44bd..a256a3305b1 100644 --- a/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts +++ b/public/app/features/dashboard-scene/scene/setDashboardPanelContext.ts @@ -6,7 +6,12 @@ import { AdHocFilterItem, PanelContext } from '@grafana/ui'; import { annotationServer } from 'app/features/annotations/api'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; -import { getDashboardSceneFor, getPanelIdForVizPanel, getQueryRunnerFor } from '../utils/utils'; +import { + getDashboardSceneFor, + getDatasourceFromQueryRunner, + getPanelIdForVizPanel, + getQueryRunnerFor, +} from '../utils/utils'; import { DashboardScene } from './DashboardScene'; @@ -121,7 +126,7 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte context.eventBus.publish(new AnnotationChangeEvent({ id })); }; - context.onAddAdHocFilter = (newFilter: AdHocFilterItem) => { + context.onAddAdHocFilter = async (newFilter: AdHocFilterItem) => { const dashboard = getDashboardSceneFor(vizPanel); const queryRunner = getQueryRunnerFor(vizPanel); @@ -129,7 +134,19 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte return; } - const filterVar = getAdHocFilterVariableFor(dashboard, queryRunner.state.datasource); + let datasource = getDatasourceFromQueryRunner(queryRunner); + + // If the datasource is type-only (e.g. it's possible that only group is set in V2 schema queries) + // we need to resolve it to a full datasource + if (datasource && !datasource.uid) { + const datasourceToLoad = await getDataSourceSrv().get(datasource); + datasource = { + uid: datasourceToLoad.uid, + type: datasourceToLoad.type, + }; + } + + const filterVar = getAdHocFilterVariableFor(dashboard, datasource); updateAdHocFilterVariable(filterVar, newFilter); }; @@ -141,7 +158,8 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte return []; } - const groupByVar = getGroupByVariableFor(dashboard, queryRunner.state.datasource); + const datasource = getDatasourceFromQueryRunner(queryRunner); + const groupByVar = getGroupByVariableFor(dashboard, datasource); if (!groupByVar) { return []; @@ -158,7 +176,7 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte .filter((item) => item !== undefined); }; - context.onAddAdHocFilters = (items: AdHocFilterItem[]) => { + context.onAddAdHocFilters = async (items: AdHocFilterItem[]) => { const dashboard = getDashboardSceneFor(vizPanel); const queryRunner = getQueryRunnerFor(vizPanel); @@ -166,7 +184,18 @@ export function setDashboardPanelContext(vizPanel: VizPanel, context: PanelConte return; } - const filterVar = getAdHocFilterVariableFor(dashboard, queryRunner.state.datasource); + let datasource = getDatasourceFromQueryRunner(queryRunner); + + // If the datasource is type-only (e.g. it's possible that only group is set in V2 schema queries) + // we need to resolve it to a full datasource + if (datasource && !datasource.uid) { + const datasourceToLoad = await getDataSourceSrv().get(datasource); + datasource = { + uid: datasourceToLoad.uid, + type: datasourceToLoad.type, + }; + } + const filterVar = getAdHocFilterVariableFor(dashboard, datasource); bulkUpdateAdHocFiltersVariable(filterVar, items); }; diff --git a/public/app/features/dashboard-scene/utils/drilldownUtils.ts b/public/app/features/dashboard-scene/utils/drilldownUtils.ts index 2ff0ecf7c6e..cf6f1271162 100644 --- a/public/app/features/dashboard-scene/utils/drilldownUtils.ts +++ b/public/app/features/dashboard-scene/utils/drilldownUtils.ts @@ -3,6 +3,8 @@ import { getDataSourceSrv } from '@grafana/runtime'; import { AdHocFiltersVariable, GroupByVariable, sceneGraph, SceneObject, SceneQueryRunner } from '@grafana/scenes'; import { DataSourceRef } from '@grafana/schema'; +import { getDatasourceFromQueryRunner } from './utils'; + export function verifyDrilldownApplicability( sourceObject: SceneObject, queriesDataSource: DataSourceRef | undefined, @@ -26,7 +28,7 @@ export async function getDrilldownApplicability( return; } - const datasource = queryRunner.state.datasource; + const datasource = getDatasourceFromQueryRunner(queryRunner); const queries = queryRunner.state.data?.request?.targets; const ds = await getDataSourceSrv().get(datasource?.uid); diff --git a/public/app/features/dashboard-scene/utils/urlBuilders.ts b/public/app/features/dashboard-scene/utils/urlBuilders.ts index 942d378a38f..11f604d6990 100644 --- a/public/app/features/dashboard-scene/utils/urlBuilders.ts +++ b/public/app/features/dashboard-scene/utils/urlBuilders.ts @@ -4,7 +4,7 @@ import { sceneGraph, VizPanel } from '@grafana/scenes'; import { contextSrv } from 'app/core/services/context_srv'; import { getExploreUrl } from 'app/core/utils/explore'; -import { getQueryRunnerFor } from './utils'; +import { getDatasourceFromQueryRunner, getQueryRunnerFor } from './utils'; export function getViewPanelUrl(vizPanel: VizPanel) { return locationUtil.getUrlForPartial(locationService.getLocation(), { @@ -27,10 +27,11 @@ export function tryGetExploreUrlForPanel(vizPanel: VizPanel): Promise